8 Commits

Author SHA1 Message Date
714320c018 Add edge routing and health check RPCs (Phase 2)
New agent RPCs for v2 multi-node orchestration:

- SetupEdgeRoute: provisions TLS cert from Metacrypt, resolves backend
  hostname to Tailnet IP, validates it's in 100.64.0.0/10, registers
  L7 route in mc-proxy. Rejects backend_tls=false.
- RemoveEdgeRoute: removes mc-proxy route, cleans up TLS cert, removes
  registry entry.
- ListEdgeRoutes: returns all edge routes with cert serial/expiry.
- HealthCheck: returns agent health and container count.

New database table (migration 4): edge_routes stores hostname, backend
info, and cert paths for persistence across agent restarts.

ProxyRouter gains CertPath/KeyPath helpers for consistent cert path
construction.

Security:
- Backend hostname must resolve to a Tailnet IP (100.64.0.0/10)
- backend_tls=false is rejected (no cleartext to backends)
- Cert provisioning failure fails the setup (no route to missing cert)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 13:13:10 -07:00
fa8ba6fac1 Move ARCHITECTURE_V2.md to metacircular docs
The v2 architecture doc is platform-wide (covers master, agents,
edge routing, snapshots, migration across all nodes). Moved to
docs/architecture-v2.md in the metacircular workspace repo.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 11:09:09 -07:00
f66758b92b Hardcode version in flake.nix
The git fetcher doesn't provide gitDescribe, so the Nix build was
falling through to shortRev and producing commit-hash versions instead
of tag-based ones.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 17:46:12 -07:00
09d0d197c3 Add component-level targeting to start, stop, and restart
Allow start/stop/restart to target a single component via
<service>/<component> syntax, matching deploy/logs/purge. When a
component is specified, start/stop skip toggling the service-level
active flag. Agent-side filtering returns NotFound for unknown
components.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 17:26:05 -07:00
52914d50b0 Pass mode, backend-tls, and tls cert/key through route add
The --mode flag was defined but never wired through to the RPC.
Add tls_cert and tls_key fields to AddProxyRouteRequest so L7
routes can be created via mcp route add.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 20:44:44 -07:00
bb4bee51ba Add mono-repo consideration to ARCHITECTURE_V2.md open questions
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 20:40:32 -07:00
4ac8a6d60b Add ARCHITECTURE_V2.md for multi-node master/agent topology
Documents the planned v2 architecture: mcp-master on straylight
coordinates deployments across worker (rift) and edge (svc) nodes.
Includes edge routing flow, agent RPCs, migration plan, and
operational issues from v1 that motivate the redesign.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 20:37:24 -07:00
d8f45ca520 Merge explicit ports with route-allocated ports during deploy
Previously, explicit port mappings from the service definition were
ignored when routes were present. Now both are included, allowing
services to have stable external port bindings alongside dynamic
route-allocated ports.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 19:28:40 -07:00
13 changed files with 1219 additions and 100 deletions

View File

@@ -14,8 +14,8 @@ import (
func stopCmd() *cobra.Command {
return &cobra.Command{
Use: "stop <service>",
Short: "Stop all components, set active=false",
Use: "stop <service>[/<component>]",
Short: "Stop components (or all), set active=false",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
cfg, err := config.LoadCLIConfig(cfgPath)
@@ -23,7 +23,7 @@ func stopCmd() *cobra.Command {
return fmt.Errorf("load config: %w", err)
}
serviceName := args[0]
serviceName, component := parseServiceArg(args[0])
defPath := filepath.Join(cfg.Services.Dir, serviceName+".toml")
def, err := servicedef.Load(defPath)
@@ -31,10 +31,13 @@ func stopCmd() *cobra.Command {
return fmt.Errorf("load service def: %w", err)
}
active := false
def.Active = &active
if err := servicedef.Write(defPath, def); err != nil {
return fmt.Errorf("write service def: %w", err)
// Only flip active=false when stopping the whole service.
if component == "" {
active := false
def.Active = &active
if err := servicedef.Write(defPath, def); err != nil {
return fmt.Errorf("write service def: %w", err)
}
}
address, err := findNodeAddress(cfg, def.Node)
@@ -49,7 +52,8 @@ func stopCmd() *cobra.Command {
defer func() { _ = conn.Close() }()
resp, err := client.StopService(context.Background(), &mcpv1.StopServiceRequest{
Name: serviceName,
Name: serviceName,
Component: component,
})
if err != nil {
return fmt.Errorf("stop service: %w", err)
@@ -63,8 +67,8 @@ func stopCmd() *cobra.Command {
func startCmd() *cobra.Command {
return &cobra.Command{
Use: "start <service>",
Short: "Start all components, set active=true",
Use: "start <service>[/<component>]",
Short: "Start components (or all), set active=true",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
cfg, err := config.LoadCLIConfig(cfgPath)
@@ -72,7 +76,7 @@ func startCmd() *cobra.Command {
return fmt.Errorf("load config: %w", err)
}
serviceName := args[0]
serviceName, component := parseServiceArg(args[0])
defPath := filepath.Join(cfg.Services.Dir, serviceName+".toml")
def, err := servicedef.Load(defPath)
@@ -80,10 +84,13 @@ func startCmd() *cobra.Command {
return fmt.Errorf("load service def: %w", err)
}
active := true
def.Active = &active
if err := servicedef.Write(defPath, def); err != nil {
return fmt.Errorf("write service def: %w", err)
// Only flip active=true when starting the whole service.
if component == "" {
active := true
def.Active = &active
if err := servicedef.Write(defPath, def); err != nil {
return fmt.Errorf("write service def: %w", err)
}
}
address, err := findNodeAddress(cfg, def.Node)
@@ -98,7 +105,8 @@ func startCmd() *cobra.Command {
defer func() { _ = conn.Close() }()
resp, err := client.StartService(context.Background(), &mcpv1.StartServiceRequest{
Name: serviceName,
Name: serviceName,
Component: component,
})
if err != nil {
return fmt.Errorf("start service: %w", err)
@@ -112,8 +120,8 @@ func startCmd() *cobra.Command {
func restartCmd() *cobra.Command {
return &cobra.Command{
Use: "restart <service>",
Short: "Restart all components",
Use: "restart <service>[/<component>]",
Short: "Restart components (or all)",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
cfg, err := config.LoadCLIConfig(cfgPath)
@@ -121,7 +129,7 @@ func restartCmd() *cobra.Command {
return fmt.Errorf("load config: %w", err)
}
serviceName := args[0]
serviceName, component := parseServiceArg(args[0])
defPath := filepath.Join(cfg.Services.Dir, serviceName+".toml")
def, err := servicedef.Load(defPath)
@@ -141,7 +149,8 @@ func restartCmd() *cobra.Command {
defer func() { _ = conn.Close() }()
resp, err := client.RestartService(context.Background(), &mcpv1.RestartServiceRequest{
Name: serviceName,
Name: serviceName,
Component: component,
})
if err != nil {
return fmt.Errorf("restart service: %w", err)

View File

@@ -28,17 +28,26 @@ func routeCmd() *cobra.Command {
},
}
var (
routeMode string
backendTLS bool
tlsCert string
tlsKey string
)
add := &cobra.Command{
Use: "add <listener> <hostname> <backend>",
Short: "Add a route to mc-proxy",
Long: "Add a route. Example: mcp route add -n rift :443 mcq.metacircular.net 100.95.252.120:443",
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)
return runRouteAdd(nodeName, args, routeMode, backendTLS, tlsCert, tlsKey)
},
}
add.Flags().String("mode", "l4", "route mode (l4 or l7)")
add.Flags().Bool("backend-tls", false, "re-encrypt traffic to backend")
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 <listener> <hostname>",
@@ -138,7 +147,7 @@ func printRoutes(nodeName string, resp *mcpv1.ListProxyRoutesResponse) {
}
}
func runRouteAdd(nodeName string, args []string) error {
func runRouteAdd(nodeName string, args []string, mode string, backendTLS bool, tlsCert, tlsKey string) error {
if nodeName == "" {
return fmt.Errorf("--node is required")
}
@@ -166,12 +175,16 @@ func runRouteAdd(nodeName string, args []string) error {
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 on %s (%s)\n", args[1], args[2], args[0], nodeName)
fmt.Printf("Added route: %s %s → %s on %s (%s)\n", mode, args[1], args[2], args[0], nodeName)
return nil
}

View File

@@ -10,7 +10,7 @@
let
system = "x86_64-linux";
pkgs = nixpkgs.legacyPackages.${system};
version = pkgs.lib.removePrefix "v" (self.gitDescribe or self.shortRev or self.dirtyShortRev or "unknown");
version = "0.8.3";
in
{
packages.${system} = {

View File

@@ -211,6 +211,7 @@ type ServiceSpec struct {
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
Active bool `protobuf:"varint,2,opt,name=active,proto3" json:"active,omitempty"`
Components []*ComponentSpec `protobuf:"bytes,3,rep,name=components,proto3" json:"components,omitempty"`
Comment string `protobuf:"bytes,4,opt,name=comment,proto3" json:"comment,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
@@ -266,6 +267,13 @@ func (x *ServiceSpec) GetComponents() []*ComponentSpec {
return nil
}
func (x *ServiceSpec) GetComment() string {
if x != nil {
return x.Comment
}
return ""
}
type DeployRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
Service *ServiceSpec `protobuf:"bytes,1,opt,name=service,proto3" json:"service,omitempty"`
@@ -426,6 +434,7 @@ func (x *ComponentResult) GetError() string {
type StopServiceRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
Component string `protobuf:"bytes,2,opt,name=component,proto3" json:"component,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
@@ -467,6 +476,13 @@ func (x *StopServiceRequest) GetName() string {
return ""
}
func (x *StopServiceRequest) GetComponent() string {
if x != nil {
return x.Component
}
return ""
}
type StopServiceResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
Results []*ComponentResult `protobuf:"bytes,1,rep,name=results,proto3" json:"results,omitempty"`
@@ -514,6 +530,7 @@ func (x *StopServiceResponse) GetResults() []*ComponentResult {
type StartServiceRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
Component string `protobuf:"bytes,2,opt,name=component,proto3" json:"component,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
@@ -555,6 +572,13 @@ func (x *StartServiceRequest) GetName() string {
return ""
}
func (x *StartServiceRequest) GetComponent() string {
if x != nil {
return x.Component
}
return ""
}
type StartServiceResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
Results []*ComponentResult `protobuf:"bytes,1,rep,name=results,proto3" json:"results,omitempty"`
@@ -602,6 +626,7 @@ func (x *StartServiceResponse) GetResults() []*ComponentResult {
type RestartServiceRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
Component string `protobuf:"bytes,2,opt,name=component,proto3" json:"component,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
@@ -643,6 +668,13 @@ func (x *RestartServiceRequest) GetName() string {
return ""
}
func (x *RestartServiceRequest) GetComponent() string {
if x != nil {
return x.Component
}
return ""
}
type RestartServiceResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
Results []*ComponentResult `protobuf:"bytes,1,rep,name=results,proto3" json:"results,omitempty"`
@@ -966,6 +998,7 @@ type ServiceInfo struct {
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
Active bool `protobuf:"varint,2,opt,name=active,proto3" json:"active,omitempty"`
Components []*ComponentInfo `protobuf:"bytes,3,rep,name=components,proto3" json:"components,omitempty"`
Comment string `protobuf:"bytes,4,opt,name=comment,proto3" json:"comment,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
@@ -1021,6 +1054,13 @@ func (x *ServiceInfo) GetComponents() []*ComponentInfo {
return nil
}
func (x *ServiceInfo) GetComment() string {
if x != nil {
return x.Comment
}
return ""
}
type ComponentInfo struct {
state protoimpl.MessageState `protogen:"open.v1"`
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
@@ -2815,6 +2855,8 @@ type AddProxyRouteRequest struct {
Backend string `protobuf:"bytes,3,opt,name=backend,proto3" json:"backend,omitempty"`
Mode string `protobuf:"bytes,4,opt,name=mode,proto3" json:"mode,omitempty"` // "l4" or "l7"
BackendTls bool `protobuf:"varint,5,opt,name=backend_tls,json=backendTls,proto3" json:"backend_tls,omitempty"`
TlsCert string `protobuf:"bytes,6,opt,name=tls_cert,json=tlsCert,proto3" json:"tls_cert,omitempty"` // path to TLS cert (required for l7)
TlsKey string `protobuf:"bytes,7,opt,name=tls_key,json=tlsKey,proto3" json:"tls_key,omitempty"` // path to TLS key (required for l7)
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
@@ -2884,6 +2926,20 @@ func (x *AddProxyRouteRequest) GetBackendTls() bool {
return false
}
func (x *AddProxyRouteRequest) GetTlsCert() string {
if x != nil {
return x.TlsCert
}
return ""
}
func (x *AddProxyRouteRequest) GetTlsKey() string {
if x != nil {
return x.TlsKey
}
return ""
}
type AddProxyRouteResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
unknownFields protoimpl.UnknownFields
@@ -3008,6 +3064,434 @@ func (*RemoveProxyRouteResponse) Descriptor() ([]byte, []int) {
return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{52}
}
type SetupEdgeRouteRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
Hostname string `protobuf:"bytes,1,opt,name=hostname,proto3" json:"hostname,omitempty"` // public hostname (e.g. "mcq.metacircular.net")
BackendHostname string `protobuf:"bytes,2,opt,name=backend_hostname,json=backendHostname,proto3" json:"backend_hostname,omitempty"` // internal .svc.mcp hostname
BackendPort int32 `protobuf:"varint,3,opt,name=backend_port,json=backendPort,proto3" json:"backend_port,omitempty"` // port on worker's mc-proxy
BackendTls bool `protobuf:"varint,4,opt,name=backend_tls,json=backendTls,proto3" json:"backend_tls,omitempty"` // MUST be true; agent rejects false
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *SetupEdgeRouteRequest) Reset() {
*x = SetupEdgeRouteRequest{}
mi := &file_proto_mcp_v1_mcp_proto_msgTypes[53]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *SetupEdgeRouteRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SetupEdgeRouteRequest) ProtoMessage() {}
func (x *SetupEdgeRouteRequest) ProtoReflect() protoreflect.Message {
mi := &file_proto_mcp_v1_mcp_proto_msgTypes[53]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SetupEdgeRouteRequest.ProtoReflect.Descriptor instead.
func (*SetupEdgeRouteRequest) Descriptor() ([]byte, []int) {
return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{53}
}
func (x *SetupEdgeRouteRequest) GetHostname() string {
if x != nil {
return x.Hostname
}
return ""
}
func (x *SetupEdgeRouteRequest) GetBackendHostname() string {
if x != nil {
return x.BackendHostname
}
return ""
}
func (x *SetupEdgeRouteRequest) GetBackendPort() int32 {
if x != nil {
return x.BackendPort
}
return 0
}
func (x *SetupEdgeRouteRequest) GetBackendTls() bool {
if x != nil {
return x.BackendTls
}
return false
}
type SetupEdgeRouteResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *SetupEdgeRouteResponse) Reset() {
*x = SetupEdgeRouteResponse{}
mi := &file_proto_mcp_v1_mcp_proto_msgTypes[54]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *SetupEdgeRouteResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SetupEdgeRouteResponse) ProtoMessage() {}
func (x *SetupEdgeRouteResponse) ProtoReflect() protoreflect.Message {
mi := &file_proto_mcp_v1_mcp_proto_msgTypes[54]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SetupEdgeRouteResponse.ProtoReflect.Descriptor instead.
func (*SetupEdgeRouteResponse) Descriptor() ([]byte, []int) {
return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{54}
}
type RemoveEdgeRouteRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
Hostname string `protobuf:"bytes,1,opt,name=hostname,proto3" json:"hostname,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *RemoveEdgeRouteRequest) Reset() {
*x = RemoveEdgeRouteRequest{}
mi := &file_proto_mcp_v1_mcp_proto_msgTypes[55]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *RemoveEdgeRouteRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*RemoveEdgeRouteRequest) ProtoMessage() {}
func (x *RemoveEdgeRouteRequest) ProtoReflect() protoreflect.Message {
mi := &file_proto_mcp_v1_mcp_proto_msgTypes[55]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use RemoveEdgeRouteRequest.ProtoReflect.Descriptor instead.
func (*RemoveEdgeRouteRequest) Descriptor() ([]byte, []int) {
return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{55}
}
func (x *RemoveEdgeRouteRequest) GetHostname() string {
if x != nil {
return x.Hostname
}
return ""
}
type RemoveEdgeRouteResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *RemoveEdgeRouteResponse) Reset() {
*x = RemoveEdgeRouteResponse{}
mi := &file_proto_mcp_v1_mcp_proto_msgTypes[56]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *RemoveEdgeRouteResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*RemoveEdgeRouteResponse) ProtoMessage() {}
func (x *RemoveEdgeRouteResponse) ProtoReflect() protoreflect.Message {
mi := &file_proto_mcp_v1_mcp_proto_msgTypes[56]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use RemoveEdgeRouteResponse.ProtoReflect.Descriptor instead.
func (*RemoveEdgeRouteResponse) Descriptor() ([]byte, []int) {
return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{56}
}
type ListEdgeRoutesRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ListEdgeRoutesRequest) Reset() {
*x = ListEdgeRoutesRequest{}
mi := &file_proto_mcp_v1_mcp_proto_msgTypes[57]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ListEdgeRoutesRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ListEdgeRoutesRequest) ProtoMessage() {}
func (x *ListEdgeRoutesRequest) ProtoReflect() protoreflect.Message {
mi := &file_proto_mcp_v1_mcp_proto_msgTypes[57]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ListEdgeRoutesRequest.ProtoReflect.Descriptor instead.
func (*ListEdgeRoutesRequest) Descriptor() ([]byte, []int) {
return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{57}
}
type ListEdgeRoutesResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
Routes []*EdgeRoute `protobuf:"bytes,1,rep,name=routes,proto3" json:"routes,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ListEdgeRoutesResponse) Reset() {
*x = ListEdgeRoutesResponse{}
mi := &file_proto_mcp_v1_mcp_proto_msgTypes[58]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ListEdgeRoutesResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ListEdgeRoutesResponse) ProtoMessage() {}
func (x *ListEdgeRoutesResponse) ProtoReflect() protoreflect.Message {
mi := &file_proto_mcp_v1_mcp_proto_msgTypes[58]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ListEdgeRoutesResponse.ProtoReflect.Descriptor instead.
func (*ListEdgeRoutesResponse) Descriptor() ([]byte, []int) {
return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{58}
}
func (x *ListEdgeRoutesResponse) GetRoutes() []*EdgeRoute {
if x != nil {
return x.Routes
}
return nil
}
type EdgeRoute struct {
state protoimpl.MessageState `protogen:"open.v1"`
Hostname string `protobuf:"bytes,1,opt,name=hostname,proto3" json:"hostname,omitempty"`
BackendHostname string `protobuf:"bytes,2,opt,name=backend_hostname,json=backendHostname,proto3" json:"backend_hostname,omitempty"`
BackendPort int32 `protobuf:"varint,3,opt,name=backend_port,json=backendPort,proto3" json:"backend_port,omitempty"`
CertSerial string `protobuf:"bytes,4,opt,name=cert_serial,json=certSerial,proto3" json:"cert_serial,omitempty"`
CertExpires string `protobuf:"bytes,5,opt,name=cert_expires,json=certExpires,proto3" json:"cert_expires,omitempty"` // RFC3339
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *EdgeRoute) Reset() {
*x = EdgeRoute{}
mi := &file_proto_mcp_v1_mcp_proto_msgTypes[59]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *EdgeRoute) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*EdgeRoute) ProtoMessage() {}
func (x *EdgeRoute) ProtoReflect() protoreflect.Message {
mi := &file_proto_mcp_v1_mcp_proto_msgTypes[59]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use EdgeRoute.ProtoReflect.Descriptor instead.
func (*EdgeRoute) Descriptor() ([]byte, []int) {
return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{59}
}
func (x *EdgeRoute) GetHostname() string {
if x != nil {
return x.Hostname
}
return ""
}
func (x *EdgeRoute) GetBackendHostname() string {
if x != nil {
return x.BackendHostname
}
return ""
}
func (x *EdgeRoute) GetBackendPort() int32 {
if x != nil {
return x.BackendPort
}
return 0
}
func (x *EdgeRoute) GetCertSerial() string {
if x != nil {
return x.CertSerial
}
return ""
}
func (x *EdgeRoute) GetCertExpires() string {
if x != nil {
return x.CertExpires
}
return ""
}
type HealthCheckRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *HealthCheckRequest) Reset() {
*x = HealthCheckRequest{}
mi := &file_proto_mcp_v1_mcp_proto_msgTypes[60]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *HealthCheckRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*HealthCheckRequest) ProtoMessage() {}
func (x *HealthCheckRequest) ProtoReflect() protoreflect.Message {
mi := &file_proto_mcp_v1_mcp_proto_msgTypes[60]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use HealthCheckRequest.ProtoReflect.Descriptor instead.
func (*HealthCheckRequest) Descriptor() ([]byte, []int) {
return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{60}
}
type HealthCheckResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` // "healthy" or "degraded"
Containers int32 `protobuf:"varint,2,opt,name=containers,proto3" json:"containers,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *HealthCheckResponse) Reset() {
*x = HealthCheckResponse{}
mi := &file_proto_mcp_v1_mcp_proto_msgTypes[61]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *HealthCheckResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*HealthCheckResponse) ProtoMessage() {}
func (x *HealthCheckResponse) ProtoReflect() protoreflect.Message {
mi := &file_proto_mcp_v1_mcp_proto_msgTypes[61]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use HealthCheckResponse.ProtoReflect.Descriptor instead.
func (*HealthCheckResponse) Descriptor() ([]byte, []int) {
return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{61}
}
func (x *HealthCheckResponse) GetStatus() string {
if x != nil {
return x.Status
}
return ""
}
func (x *HealthCheckResponse) GetContainers() int32 {
if x != nil {
return x.Containers
}
return 0
}
var File_proto_mcp_v1_mcp_proto protoreflect.FileDescriptor
const file_proto_mcp_v1_mcp_proto_rawDesc = "" +
@@ -3029,13 +3513,14 @@ const file_proto_mcp_v1_mcp_proto_rawDesc = "" +
"\x03cmd\x18\b \x03(\tR\x03cmd\x12)\n" +
"\x06routes\x18\t \x03(\v2\x11.mcp.v1.RouteSpecR\x06routes\x12\x10\n" +
"\x03env\x18\n" +
" \x03(\tR\x03env\"p\n" +
" \x03(\tR\x03env\"\x8a\x01\n" +
"\vServiceSpec\x12\x12\n" +
"\x04name\x18\x01 \x01(\tR\x04name\x12\x16\n" +
"\x06active\x18\x02 \x01(\bR\x06active\x125\n" +
"\n" +
"components\x18\x03 \x03(\v2\x15.mcp.v1.ComponentSpecR\n" +
"components\"\\\n" +
"components\x12\x18\n" +
"\acomment\x18\x04 \x01(\tR\acomment\"\\\n" +
"\rDeployRequest\x12-\n" +
"\aservice\x18\x01 \x01(\v2\x13.mcp.v1.ServiceSpecR\aservice\x12\x1c\n" +
"\tcomponent\x18\x02 \x01(\tR\tcomponent\"C\n" +
@@ -3044,17 +3529,20 @@ const file_proto_mcp_v1_mcp_proto_rawDesc = "" +
"\x0fComponentResult\x12\x12\n" +
"\x04name\x18\x01 \x01(\tR\x04name\x12\x18\n" +
"\asuccess\x18\x02 \x01(\bR\asuccess\x12\x14\n" +
"\x05error\x18\x03 \x01(\tR\x05error\"(\n" +
"\x05error\x18\x03 \x01(\tR\x05error\"F\n" +
"\x12StopServiceRequest\x12\x12\n" +
"\x04name\x18\x01 \x01(\tR\x04name\"H\n" +
"\x04name\x18\x01 \x01(\tR\x04name\x12\x1c\n" +
"\tcomponent\x18\x02 \x01(\tR\tcomponent\"H\n" +
"\x13StopServiceResponse\x121\n" +
"\aresults\x18\x01 \x03(\v2\x17.mcp.v1.ComponentResultR\aresults\")\n" +
"\aresults\x18\x01 \x03(\v2\x17.mcp.v1.ComponentResultR\aresults\"G\n" +
"\x13StartServiceRequest\x12\x12\n" +
"\x04name\x18\x01 \x01(\tR\x04name\"I\n" +
"\x04name\x18\x01 \x01(\tR\x04name\x12\x1c\n" +
"\tcomponent\x18\x02 \x01(\tR\tcomponent\"I\n" +
"\x14StartServiceResponse\x121\n" +
"\aresults\x18\x01 \x03(\v2\x17.mcp.v1.ComponentResultR\aresults\"+\n" +
"\aresults\x18\x01 \x03(\v2\x17.mcp.v1.ComponentResultR\aresults\"I\n" +
"\x15RestartServiceRequest\x12\x12\n" +
"\x04name\x18\x01 \x01(\tR\x04name\"K\n" +
"\x04name\x18\x01 \x01(\tR\x04name\x12\x1c\n" +
"\tcomponent\x18\x02 \x01(\tR\tcomponent\"K\n" +
"\x16RestartServiceResponse\x121\n" +
"\aresults\x18\x01 \x03(\v2\x17.mcp.v1.ComponentResultR\aresults\",\n" +
"\x16UndeployServiceRequest\x12\x12\n" +
@@ -3069,13 +3557,14 @@ const file_proto_mcp_v1_mcp_proto_rawDesc = "" +
"\x04name\x18\x01 \x01(\tR\x04name\x12\x18\n" +
"\achanged\x18\x02 \x01(\bR\achanged\x12\x18\n" +
"\asummary\x18\x03 \x01(\tR\asummary\"\x15\n" +
"\x13ListServicesRequest\"p\n" +
"\x13ListServicesRequest\"\x8a\x01\n" +
"\vServiceInfo\x12\x12\n" +
"\x04name\x18\x01 \x01(\tR\x04name\x12\x16\n" +
"\x06active\x18\x02 \x01(\bR\x06active\x125\n" +
"\n" +
"components\x18\x03 \x03(\v2\x15.mcp.v1.ComponentInfoR\n" +
"components\"\xd5\x01\n" +
"components\x12\x18\n" +
"\acomment\x18\x04 \x01(\tR\acomment\"\xd5\x01\n" +
"\rComponentInfo\x12\x12\n" +
"\x04name\x18\x01 \x01(\tR\x04name\x12\x14\n" +
"\x05image\x18\x02 \x01(\tR\x05image\x12#\n" +
@@ -3198,19 +3687,47 @@ const file_proto_mcp_v1_mcp_proto_rawDesc = "" +
"\x11total_connections\x18\x02 \x01(\x03R\x10totalConnections\x129\n" +
"\n" +
"started_at\x18\x03 \x01(\v2\x1a.google.protobuf.TimestampR\tstartedAt\x127\n" +
"\tlisteners\x18\x04 \x03(\v2\x19.mcp.v1.ProxyListenerInfoR\tlisteners\"\xa6\x01\n" +
"\tlisteners\x18\x04 \x03(\v2\x19.mcp.v1.ProxyListenerInfoR\tlisteners\"\xda\x01\n" +
"\x14AddProxyRouteRequest\x12#\n" +
"\rlistener_addr\x18\x01 \x01(\tR\flistenerAddr\x12\x1a\n" +
"\bhostname\x18\x02 \x01(\tR\bhostname\x12\x18\n" +
"\abackend\x18\x03 \x01(\tR\abackend\x12\x12\n" +
"\x04mode\x18\x04 \x01(\tR\x04mode\x12\x1f\n" +
"\vbackend_tls\x18\x05 \x01(\bR\n" +
"backendTls\"\x17\n" +
"backendTls\x12\x19\n" +
"\btls_cert\x18\x06 \x01(\tR\atlsCert\x12\x17\n" +
"\atls_key\x18\a \x01(\tR\x06tlsKey\"\x17\n" +
"\x15AddProxyRouteResponse\"Z\n" +
"\x17RemoveProxyRouteRequest\x12#\n" +
"\rlistener_addr\x18\x01 \x01(\tR\flistenerAddr\x12\x1a\n" +
"\bhostname\x18\x02 \x01(\tR\bhostname\"\x1a\n" +
"\x18RemoveProxyRouteResponse2\x92\v\n" +
"\x18RemoveProxyRouteResponse\"\xa2\x01\n" +
"\x15SetupEdgeRouteRequest\x12\x1a\n" +
"\bhostname\x18\x01 \x01(\tR\bhostname\x12)\n" +
"\x10backend_hostname\x18\x02 \x01(\tR\x0fbackendHostname\x12!\n" +
"\fbackend_port\x18\x03 \x01(\x05R\vbackendPort\x12\x1f\n" +
"\vbackend_tls\x18\x04 \x01(\bR\n" +
"backendTls\"\x18\n" +
"\x16SetupEdgeRouteResponse\"4\n" +
"\x16RemoveEdgeRouteRequest\x12\x1a\n" +
"\bhostname\x18\x01 \x01(\tR\bhostname\"\x19\n" +
"\x17RemoveEdgeRouteResponse\"\x17\n" +
"\x15ListEdgeRoutesRequest\"C\n" +
"\x16ListEdgeRoutesResponse\x12)\n" +
"\x06routes\x18\x01 \x03(\v2\x11.mcp.v1.EdgeRouteR\x06routes\"\xb9\x01\n" +
"\tEdgeRoute\x12\x1a\n" +
"\bhostname\x18\x01 \x01(\tR\bhostname\x12)\n" +
"\x10backend_hostname\x18\x02 \x01(\tR\x0fbackendHostname\x12!\n" +
"\fbackend_port\x18\x03 \x01(\x05R\vbackendPort\x12\x1f\n" +
"\vcert_serial\x18\x04 \x01(\tR\n" +
"certSerial\x12!\n" +
"\fcert_expires\x18\x05 \x01(\tR\vcertExpires\"\x14\n" +
"\x12HealthCheckRequest\"M\n" +
"\x13HealthCheckResponse\x12\x16\n" +
"\x06status\x18\x01 \x01(\tR\x06status\x12\x1e\n" +
"\n" +
"containers\x18\x02 \x01(\x05R\n" +
"containers2\xd0\r\n" +
"\x0fMcpAgentService\x127\n" +
"\x06Deploy\x12\x15.mcp.v1.DeployRequest\x1a\x16.mcp.v1.DeployResponse\x12R\n" +
"\x0fUndeployService\x12\x1e.mcp.v1.UndeployServiceRequest\x1a\x1f.mcp.v1.UndeployServiceResponse\x12F\n" +
@@ -3230,7 +3747,11 @@ const file_proto_mcp_v1_mcp_proto_rawDesc = "" +
"\x0eListDNSRecords\x12\x1d.mcp.v1.ListDNSRecordsRequest\x1a\x1e.mcp.v1.ListDNSRecordsResponse\x12R\n" +
"\x0fListProxyRoutes\x12\x1e.mcp.v1.ListProxyRoutesRequest\x1a\x1f.mcp.v1.ListProxyRoutesResponse\x12L\n" +
"\rAddProxyRoute\x12\x1c.mcp.v1.AddProxyRouteRequest\x1a\x1d.mcp.v1.AddProxyRouteResponse\x12U\n" +
"\x10RemoveProxyRoute\x12\x1f.mcp.v1.RemoveProxyRouteRequest\x1a .mcp.v1.RemoveProxyRouteResponse\x123\n" +
"\x10RemoveProxyRoute\x12\x1f.mcp.v1.RemoveProxyRouteRequest\x1a .mcp.v1.RemoveProxyRouteResponse\x12O\n" +
"\x0eSetupEdgeRoute\x12\x1d.mcp.v1.SetupEdgeRouteRequest\x1a\x1e.mcp.v1.SetupEdgeRouteResponse\x12R\n" +
"\x0fRemoveEdgeRoute\x12\x1e.mcp.v1.RemoveEdgeRouteRequest\x1a\x1f.mcp.v1.RemoveEdgeRouteResponse\x12O\n" +
"\x0eListEdgeRoutes\x12\x1d.mcp.v1.ListEdgeRoutesRequest\x1a\x1e.mcp.v1.ListEdgeRoutesResponse\x12F\n" +
"\vHealthCheck\x12\x1a.mcp.v1.HealthCheckRequest\x1a\x1b.mcp.v1.HealthCheckResponse\x123\n" +
"\x04Logs\x12\x13.mcp.v1.LogsRequest\x1a\x14.mcp.v1.LogsResponse0\x01B*Z(git.wntrmute.dev/mc/mcp/gen/mcp/v1;mcpv1b\x06proto3"
var (
@@ -3245,7 +3766,7 @@ func file_proto_mcp_v1_mcp_proto_rawDescGZIP() []byte {
return file_proto_mcp_v1_mcp_proto_rawDescData
}
var file_proto_mcp_v1_mcp_proto_msgTypes = make([]protoimpl.MessageInfo, 53)
var file_proto_mcp_v1_mcp_proto_msgTypes = make([]protoimpl.MessageInfo, 62)
var file_proto_mcp_v1_mcp_proto_goTypes = []any{
(*RouteSpec)(nil), // 0: mcp.v1.RouteSpec
(*ComponentSpec)(nil), // 1: mcp.v1.ComponentSpec
@@ -3300,7 +3821,16 @@ var file_proto_mcp_v1_mcp_proto_goTypes = []any{
(*AddProxyRouteResponse)(nil), // 50: mcp.v1.AddProxyRouteResponse
(*RemoveProxyRouteRequest)(nil), // 51: mcp.v1.RemoveProxyRouteRequest
(*RemoveProxyRouteResponse)(nil), // 52: mcp.v1.RemoveProxyRouteResponse
(*timestamppb.Timestamp)(nil), // 53: google.protobuf.Timestamp
(*SetupEdgeRouteRequest)(nil), // 53: mcp.v1.SetupEdgeRouteRequest
(*SetupEdgeRouteResponse)(nil), // 54: mcp.v1.SetupEdgeRouteResponse
(*RemoveEdgeRouteRequest)(nil), // 55: mcp.v1.RemoveEdgeRouteRequest
(*RemoveEdgeRouteResponse)(nil), // 56: mcp.v1.RemoveEdgeRouteResponse
(*ListEdgeRoutesRequest)(nil), // 57: mcp.v1.ListEdgeRoutesRequest
(*ListEdgeRoutesResponse)(nil), // 58: mcp.v1.ListEdgeRoutesResponse
(*EdgeRoute)(nil), // 59: mcp.v1.EdgeRoute
(*HealthCheckRequest)(nil), // 60: mcp.v1.HealthCheckRequest
(*HealthCheckResponse)(nil), // 61: mcp.v1.HealthCheckResponse
(*timestamppb.Timestamp)(nil), // 62: google.protobuf.Timestamp
}
var file_proto_mcp_v1_mcp_proto_depIdxs = []int32{
0, // 0: mcp.v1.ComponentSpec.routes:type_name -> mcp.v1.RouteSpec
@@ -3314,64 +3844,73 @@ var file_proto_mcp_v1_mcp_proto_depIdxs = []int32{
2, // 8: mcp.v1.SyncDesiredStateRequest.services:type_name -> mcp.v1.ServiceSpec
16, // 9: mcp.v1.SyncDesiredStateResponse.results:type_name -> mcp.v1.ServiceSyncResult
19, // 10: mcp.v1.ServiceInfo.components:type_name -> mcp.v1.ComponentInfo
53, // 11: mcp.v1.ComponentInfo.started:type_name -> google.protobuf.Timestamp
62, // 11: mcp.v1.ComponentInfo.started:type_name -> google.protobuf.Timestamp
18, // 12: mcp.v1.ListServicesResponse.services:type_name -> mcp.v1.ServiceInfo
53, // 13: mcp.v1.EventInfo.timestamp:type_name -> google.protobuf.Timestamp
62, // 13: mcp.v1.EventInfo.timestamp:type_name -> google.protobuf.Timestamp
18, // 14: mcp.v1.GetServiceStatusResponse.services:type_name -> mcp.v1.ServiceInfo
22, // 15: mcp.v1.GetServiceStatusResponse.drift:type_name -> mcp.v1.DriftInfo
23, // 16: mcp.v1.GetServiceStatusResponse.recent_events:type_name -> mcp.v1.EventInfo
18, // 17: mcp.v1.LiveCheckResponse.services:type_name -> mcp.v1.ServiceInfo
28, // 18: mcp.v1.AdoptContainersResponse.results:type_name -> mcp.v1.AdoptResult
53, // 19: mcp.v1.NodeStatusResponse.uptime_since:type_name -> google.protobuf.Timestamp
62, // 19: mcp.v1.NodeStatusResponse.uptime_since:type_name -> google.protobuf.Timestamp
38, // 20: mcp.v1.PurgeResponse.results:type_name -> mcp.v1.PurgeResult
43, // 21: mcp.v1.DNSZone.records:type_name -> mcp.v1.DNSRecord
42, // 22: mcp.v1.ListDNSRecordsResponse.zones:type_name -> mcp.v1.DNSZone
46, // 23: mcp.v1.ProxyListenerInfo.routes:type_name -> mcp.v1.ProxyRouteInfo
53, // 24: mcp.v1.ListProxyRoutesResponse.started_at:type_name -> google.protobuf.Timestamp
62, // 24: mcp.v1.ListProxyRoutesResponse.started_at:type_name -> google.protobuf.Timestamp
47, // 25: mcp.v1.ListProxyRoutesResponse.listeners:type_name -> mcp.v1.ProxyListenerInfo
3, // 26: mcp.v1.McpAgentService.Deploy:input_type -> mcp.v1.DeployRequest
12, // 27: mcp.v1.McpAgentService.UndeployService:input_type -> mcp.v1.UndeployServiceRequest
6, // 28: mcp.v1.McpAgentService.StopService:input_type -> mcp.v1.StopServiceRequest
8, // 29: mcp.v1.McpAgentService.StartService:input_type -> mcp.v1.StartServiceRequest
10, // 30: mcp.v1.McpAgentService.RestartService:input_type -> mcp.v1.RestartServiceRequest
14, // 31: mcp.v1.McpAgentService.SyncDesiredState:input_type -> mcp.v1.SyncDesiredStateRequest
17, // 32: mcp.v1.McpAgentService.ListServices:input_type -> mcp.v1.ListServicesRequest
21, // 33: mcp.v1.McpAgentService.GetServiceStatus:input_type -> mcp.v1.GetServiceStatusRequest
25, // 34: mcp.v1.McpAgentService.LiveCheck:input_type -> mcp.v1.LiveCheckRequest
27, // 35: mcp.v1.McpAgentService.AdoptContainers:input_type -> mcp.v1.AdoptContainersRequest
36, // 36: mcp.v1.McpAgentService.PurgeComponent:input_type -> mcp.v1.PurgeRequest
30, // 37: mcp.v1.McpAgentService.PushFile:input_type -> mcp.v1.PushFileRequest
32, // 38: mcp.v1.McpAgentService.PullFile:input_type -> mcp.v1.PullFileRequest
34, // 39: mcp.v1.McpAgentService.NodeStatus:input_type -> mcp.v1.NodeStatusRequest
41, // 40: mcp.v1.McpAgentService.ListDNSRecords:input_type -> mcp.v1.ListDNSRecordsRequest
45, // 41: mcp.v1.McpAgentService.ListProxyRoutes:input_type -> mcp.v1.ListProxyRoutesRequest
49, // 42: mcp.v1.McpAgentService.AddProxyRoute:input_type -> mcp.v1.AddProxyRouteRequest
51, // 43: mcp.v1.McpAgentService.RemoveProxyRoute:input_type -> mcp.v1.RemoveProxyRouteRequest
39, // 44: mcp.v1.McpAgentService.Logs:input_type -> mcp.v1.LogsRequest
4, // 45: mcp.v1.McpAgentService.Deploy:output_type -> mcp.v1.DeployResponse
13, // 46: mcp.v1.McpAgentService.UndeployService:output_type -> mcp.v1.UndeployServiceResponse
7, // 47: mcp.v1.McpAgentService.StopService:output_type -> mcp.v1.StopServiceResponse
9, // 48: mcp.v1.McpAgentService.StartService:output_type -> mcp.v1.StartServiceResponse
11, // 49: mcp.v1.McpAgentService.RestartService:output_type -> mcp.v1.RestartServiceResponse
15, // 50: mcp.v1.McpAgentService.SyncDesiredState:output_type -> mcp.v1.SyncDesiredStateResponse
20, // 51: mcp.v1.McpAgentService.ListServices:output_type -> mcp.v1.ListServicesResponse
24, // 52: mcp.v1.McpAgentService.GetServiceStatus:output_type -> mcp.v1.GetServiceStatusResponse
26, // 53: mcp.v1.McpAgentService.LiveCheck:output_type -> mcp.v1.LiveCheckResponse
29, // 54: mcp.v1.McpAgentService.AdoptContainers:output_type -> mcp.v1.AdoptContainersResponse
37, // 55: mcp.v1.McpAgentService.PurgeComponent:output_type -> mcp.v1.PurgeResponse
31, // 56: mcp.v1.McpAgentService.PushFile:output_type -> mcp.v1.PushFileResponse
33, // 57: mcp.v1.McpAgentService.PullFile:output_type -> mcp.v1.PullFileResponse
35, // 58: mcp.v1.McpAgentService.NodeStatus:output_type -> mcp.v1.NodeStatusResponse
44, // 59: mcp.v1.McpAgentService.ListDNSRecords:output_type -> mcp.v1.ListDNSRecordsResponse
48, // 60: mcp.v1.McpAgentService.ListProxyRoutes:output_type -> mcp.v1.ListProxyRoutesResponse
50, // 61: mcp.v1.McpAgentService.AddProxyRoute:output_type -> mcp.v1.AddProxyRouteResponse
52, // 62: mcp.v1.McpAgentService.RemoveProxyRoute:output_type -> mcp.v1.RemoveProxyRouteResponse
40, // 63: mcp.v1.McpAgentService.Logs:output_type -> mcp.v1.LogsResponse
45, // [45:64] is the sub-list for method output_type
26, // [26:45] is the sub-list for method input_type
26, // [26:26] is the sub-list for extension type_name
26, // [26:26] is the sub-list for extension extendee
0, // [0:26] is the sub-list for field type_name
59, // 26: mcp.v1.ListEdgeRoutesResponse.routes:type_name -> mcp.v1.EdgeRoute
3, // 27: mcp.v1.McpAgentService.Deploy:input_type -> mcp.v1.DeployRequest
12, // 28: mcp.v1.McpAgentService.UndeployService:input_type -> mcp.v1.UndeployServiceRequest
6, // 29: mcp.v1.McpAgentService.StopService:input_type -> mcp.v1.StopServiceRequest
8, // 30: mcp.v1.McpAgentService.StartService:input_type -> mcp.v1.StartServiceRequest
10, // 31: mcp.v1.McpAgentService.RestartService:input_type -> mcp.v1.RestartServiceRequest
14, // 32: mcp.v1.McpAgentService.SyncDesiredState:input_type -> mcp.v1.SyncDesiredStateRequest
17, // 33: mcp.v1.McpAgentService.ListServices:input_type -> mcp.v1.ListServicesRequest
21, // 34: mcp.v1.McpAgentService.GetServiceStatus:input_type -> mcp.v1.GetServiceStatusRequest
25, // 35: mcp.v1.McpAgentService.LiveCheck:input_type -> mcp.v1.LiveCheckRequest
27, // 36: mcp.v1.McpAgentService.AdoptContainers:input_type -> mcp.v1.AdoptContainersRequest
36, // 37: mcp.v1.McpAgentService.PurgeComponent:input_type -> mcp.v1.PurgeRequest
30, // 38: mcp.v1.McpAgentService.PushFile:input_type -> mcp.v1.PushFileRequest
32, // 39: mcp.v1.McpAgentService.PullFile:input_type -> mcp.v1.PullFileRequest
34, // 40: mcp.v1.McpAgentService.NodeStatus:input_type -> mcp.v1.NodeStatusRequest
41, // 41: mcp.v1.McpAgentService.ListDNSRecords:input_type -> mcp.v1.ListDNSRecordsRequest
45, // 42: mcp.v1.McpAgentService.ListProxyRoutes:input_type -> mcp.v1.ListProxyRoutesRequest
49, // 43: mcp.v1.McpAgentService.AddProxyRoute:input_type -> mcp.v1.AddProxyRouteRequest
51, // 44: mcp.v1.McpAgentService.RemoveProxyRoute:input_type -> mcp.v1.RemoveProxyRouteRequest
53, // 45: mcp.v1.McpAgentService.SetupEdgeRoute:input_type -> mcp.v1.SetupEdgeRouteRequest
55, // 46: mcp.v1.McpAgentService.RemoveEdgeRoute:input_type -> mcp.v1.RemoveEdgeRouteRequest
57, // 47: mcp.v1.McpAgentService.ListEdgeRoutes:input_type -> mcp.v1.ListEdgeRoutesRequest
60, // 48: mcp.v1.McpAgentService.HealthCheck:input_type -> mcp.v1.HealthCheckRequest
39, // 49: mcp.v1.McpAgentService.Logs:input_type -> mcp.v1.LogsRequest
4, // 50: mcp.v1.McpAgentService.Deploy:output_type -> mcp.v1.DeployResponse
13, // 51: mcp.v1.McpAgentService.UndeployService:output_type -> mcp.v1.UndeployServiceResponse
7, // 52: mcp.v1.McpAgentService.StopService:output_type -> mcp.v1.StopServiceResponse
9, // 53: mcp.v1.McpAgentService.StartService:output_type -> mcp.v1.StartServiceResponse
11, // 54: mcp.v1.McpAgentService.RestartService:output_type -> mcp.v1.RestartServiceResponse
15, // 55: mcp.v1.McpAgentService.SyncDesiredState:output_type -> mcp.v1.SyncDesiredStateResponse
20, // 56: mcp.v1.McpAgentService.ListServices:output_type -> mcp.v1.ListServicesResponse
24, // 57: mcp.v1.McpAgentService.GetServiceStatus:output_type -> mcp.v1.GetServiceStatusResponse
26, // 58: mcp.v1.McpAgentService.LiveCheck:output_type -> mcp.v1.LiveCheckResponse
29, // 59: mcp.v1.McpAgentService.AdoptContainers:output_type -> mcp.v1.AdoptContainersResponse
37, // 60: mcp.v1.McpAgentService.PurgeComponent:output_type -> mcp.v1.PurgeResponse
31, // 61: mcp.v1.McpAgentService.PushFile:output_type -> mcp.v1.PushFileResponse
33, // 62: mcp.v1.McpAgentService.PullFile:output_type -> mcp.v1.PullFileResponse
35, // 63: mcp.v1.McpAgentService.NodeStatus:output_type -> mcp.v1.NodeStatusResponse
44, // 64: mcp.v1.McpAgentService.ListDNSRecords:output_type -> mcp.v1.ListDNSRecordsResponse
48, // 65: mcp.v1.McpAgentService.ListProxyRoutes:output_type -> mcp.v1.ListProxyRoutesResponse
50, // 66: mcp.v1.McpAgentService.AddProxyRoute:output_type -> mcp.v1.AddProxyRouteResponse
52, // 67: mcp.v1.McpAgentService.RemoveProxyRoute:output_type -> mcp.v1.RemoveProxyRouteResponse
54, // 68: mcp.v1.McpAgentService.SetupEdgeRoute:output_type -> mcp.v1.SetupEdgeRouteResponse
56, // 69: mcp.v1.McpAgentService.RemoveEdgeRoute:output_type -> mcp.v1.RemoveEdgeRouteResponse
58, // 70: mcp.v1.McpAgentService.ListEdgeRoutes:output_type -> mcp.v1.ListEdgeRoutesResponse
61, // 71: mcp.v1.McpAgentService.HealthCheck:output_type -> mcp.v1.HealthCheckResponse
40, // 72: mcp.v1.McpAgentService.Logs:output_type -> mcp.v1.LogsResponse
50, // [50:73] is the sub-list for method output_type
27, // [27:50] is the sub-list for method input_type
27, // [27:27] is the sub-list for extension type_name
27, // [27:27] is the sub-list for extension extendee
0, // [0:27] is the sub-list for field type_name
}
func init() { file_proto_mcp_v1_mcp_proto_init() }
@@ -3385,7 +3924,7 @@ func file_proto_mcp_v1_mcp_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_mcp_v1_mcp_proto_rawDesc), len(file_proto_mcp_v1_mcp_proto_rawDesc)),
NumEnums: 0,
NumMessages: 53,
NumMessages: 62,
NumExtensions: 0,
NumServices: 1,
},

View File

@@ -37,6 +37,10 @@ const (
McpAgentService_ListProxyRoutes_FullMethodName = "/mcp.v1.McpAgentService/ListProxyRoutes"
McpAgentService_AddProxyRoute_FullMethodName = "/mcp.v1.McpAgentService/AddProxyRoute"
McpAgentService_RemoveProxyRoute_FullMethodName = "/mcp.v1.McpAgentService/RemoveProxyRoute"
McpAgentService_SetupEdgeRoute_FullMethodName = "/mcp.v1.McpAgentService/SetupEdgeRoute"
McpAgentService_RemoveEdgeRoute_FullMethodName = "/mcp.v1.McpAgentService/RemoveEdgeRoute"
McpAgentService_ListEdgeRoutes_FullMethodName = "/mcp.v1.McpAgentService/ListEdgeRoutes"
McpAgentService_HealthCheck_FullMethodName = "/mcp.v1.McpAgentService/HealthCheck"
McpAgentService_Logs_FullMethodName = "/mcp.v1.McpAgentService/Logs"
)
@@ -71,6 +75,12 @@ type McpAgentServiceClient interface {
ListProxyRoutes(ctx context.Context, in *ListProxyRoutesRequest, opts ...grpc.CallOption) (*ListProxyRoutesResponse, error)
AddProxyRoute(ctx context.Context, in *AddProxyRouteRequest, opts ...grpc.CallOption) (*AddProxyRouteResponse, error)
RemoveProxyRoute(ctx context.Context, in *RemoveProxyRouteRequest, opts ...grpc.CallOption) (*RemoveProxyRouteResponse, error)
// Edge routing (called by master on edge nodes)
SetupEdgeRoute(ctx context.Context, in *SetupEdgeRouteRequest, opts ...grpc.CallOption) (*SetupEdgeRouteResponse, error)
RemoveEdgeRoute(ctx context.Context, in *RemoveEdgeRouteRequest, opts ...grpc.CallOption) (*RemoveEdgeRouteResponse, error)
ListEdgeRoutes(ctx context.Context, in *ListEdgeRoutesRequest, opts ...grpc.CallOption) (*ListEdgeRoutesResponse, error)
// Health (called by master on missed heartbeats)
HealthCheck(ctx context.Context, in *HealthCheckRequest, opts ...grpc.CallOption) (*HealthCheckResponse, error)
// Logs
Logs(ctx context.Context, in *LogsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[LogsResponse], error)
}
@@ -263,6 +273,46 @@ func (c *mcpAgentServiceClient) RemoveProxyRoute(ctx context.Context, in *Remove
return out, nil
}
func (c *mcpAgentServiceClient) SetupEdgeRoute(ctx context.Context, in *SetupEdgeRouteRequest, opts ...grpc.CallOption) (*SetupEdgeRouteResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(SetupEdgeRouteResponse)
err := c.cc.Invoke(ctx, McpAgentService_SetupEdgeRoute_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *mcpAgentServiceClient) RemoveEdgeRoute(ctx context.Context, in *RemoveEdgeRouteRequest, opts ...grpc.CallOption) (*RemoveEdgeRouteResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(RemoveEdgeRouteResponse)
err := c.cc.Invoke(ctx, McpAgentService_RemoveEdgeRoute_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *mcpAgentServiceClient) ListEdgeRoutes(ctx context.Context, in *ListEdgeRoutesRequest, opts ...grpc.CallOption) (*ListEdgeRoutesResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(ListEdgeRoutesResponse)
err := c.cc.Invoke(ctx, McpAgentService_ListEdgeRoutes_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *mcpAgentServiceClient) HealthCheck(ctx context.Context, in *HealthCheckRequest, opts ...grpc.CallOption) (*HealthCheckResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(HealthCheckResponse)
err := c.cc.Invoke(ctx, McpAgentService_HealthCheck_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *mcpAgentServiceClient) Logs(ctx context.Context, in *LogsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[LogsResponse], error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
stream, err := c.cc.NewStream(ctx, &McpAgentService_ServiceDesc.Streams[0], McpAgentService_Logs_FullMethodName, cOpts...)
@@ -313,6 +363,12 @@ type McpAgentServiceServer interface {
ListProxyRoutes(context.Context, *ListProxyRoutesRequest) (*ListProxyRoutesResponse, error)
AddProxyRoute(context.Context, *AddProxyRouteRequest) (*AddProxyRouteResponse, error)
RemoveProxyRoute(context.Context, *RemoveProxyRouteRequest) (*RemoveProxyRouteResponse, error)
// Edge routing (called by master on edge nodes)
SetupEdgeRoute(context.Context, *SetupEdgeRouteRequest) (*SetupEdgeRouteResponse, error)
RemoveEdgeRoute(context.Context, *RemoveEdgeRouteRequest) (*RemoveEdgeRouteResponse, error)
ListEdgeRoutes(context.Context, *ListEdgeRoutesRequest) (*ListEdgeRoutesResponse, error)
// Health (called by master on missed heartbeats)
HealthCheck(context.Context, *HealthCheckRequest) (*HealthCheckResponse, error)
// Logs
Logs(*LogsRequest, grpc.ServerStreamingServer[LogsResponse]) error
mustEmbedUnimplementedMcpAgentServiceServer()
@@ -379,6 +435,18 @@ func (UnimplementedMcpAgentServiceServer) AddProxyRoute(context.Context, *AddPro
func (UnimplementedMcpAgentServiceServer) RemoveProxyRoute(context.Context, *RemoveProxyRouteRequest) (*RemoveProxyRouteResponse, error) {
return nil, status.Error(codes.Unimplemented, "method RemoveProxyRoute not implemented")
}
func (UnimplementedMcpAgentServiceServer) SetupEdgeRoute(context.Context, *SetupEdgeRouteRequest) (*SetupEdgeRouteResponse, error) {
return nil, status.Error(codes.Unimplemented, "method SetupEdgeRoute not implemented")
}
func (UnimplementedMcpAgentServiceServer) RemoveEdgeRoute(context.Context, *RemoveEdgeRouteRequest) (*RemoveEdgeRouteResponse, error) {
return nil, status.Error(codes.Unimplemented, "method RemoveEdgeRoute not implemented")
}
func (UnimplementedMcpAgentServiceServer) ListEdgeRoutes(context.Context, *ListEdgeRoutesRequest) (*ListEdgeRoutesResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ListEdgeRoutes not implemented")
}
func (UnimplementedMcpAgentServiceServer) HealthCheck(context.Context, *HealthCheckRequest) (*HealthCheckResponse, error) {
return nil, status.Error(codes.Unimplemented, "method HealthCheck not implemented")
}
func (UnimplementedMcpAgentServiceServer) Logs(*LogsRequest, grpc.ServerStreamingServer[LogsResponse]) error {
return status.Error(codes.Unimplemented, "method Logs not implemented")
}
@@ -727,6 +795,78 @@ func _McpAgentService_RemoveProxyRoute_Handler(srv interface{}, ctx context.Cont
return interceptor(ctx, in, info, handler)
}
func _McpAgentService_SetupEdgeRoute_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(SetupEdgeRouteRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(McpAgentServiceServer).SetupEdgeRoute(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: McpAgentService_SetupEdgeRoute_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(McpAgentServiceServer).SetupEdgeRoute(ctx, req.(*SetupEdgeRouteRequest))
}
return interceptor(ctx, in, info, handler)
}
func _McpAgentService_RemoveEdgeRoute_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(RemoveEdgeRouteRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(McpAgentServiceServer).RemoveEdgeRoute(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: McpAgentService_RemoveEdgeRoute_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(McpAgentServiceServer).RemoveEdgeRoute(ctx, req.(*RemoveEdgeRouteRequest))
}
return interceptor(ctx, in, info, handler)
}
func _McpAgentService_ListEdgeRoutes_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ListEdgeRoutesRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(McpAgentServiceServer).ListEdgeRoutes(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: McpAgentService_ListEdgeRoutes_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(McpAgentServiceServer).ListEdgeRoutes(ctx, req.(*ListEdgeRoutesRequest))
}
return interceptor(ctx, in, info, handler)
}
func _McpAgentService_HealthCheck_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(HealthCheckRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(McpAgentServiceServer).HealthCheck(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: McpAgentService_HealthCheck_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(McpAgentServiceServer).HealthCheck(ctx, req.(*HealthCheckRequest))
}
return interceptor(ctx, in, info, handler)
}
func _McpAgentService_Logs_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(LogsRequest)
if err := stream.RecvMsg(m); err != nil {
@@ -817,6 +957,22 @@ var McpAgentService_ServiceDesc = grpc.ServiceDesc{
MethodName: "RemoveProxyRoute",
Handler: _McpAgentService_RemoveProxyRoute_Handler,
},
{
MethodName: "SetupEdgeRoute",
Handler: _McpAgentService_SetupEdgeRoute_Handler,
},
{
MethodName: "RemoveEdgeRoute",
Handler: _McpAgentService_RemoveEdgeRoute_Handler,
},
{
MethodName: "ListEdgeRoutes",
Handler: _McpAgentService_ListEdgeRoutes_Handler,
},
{
MethodName: "HealthCheck",
Handler: _McpAgentService_HealthCheck_Handler,
},
},
Streams: []grpc.StreamDesc{
{

View File

@@ -134,7 +134,8 @@ func (a *Agent) deployComponent(ctx context.Context, serviceName string, cs *mcp
Error: fmt.Sprintf("allocate route ports: %v", err),
}
}
runSpec.Ports = ports
// Merge explicit ports from the spec with route-allocated ports.
runSpec.Ports = append(cs.GetPorts(), ports...)
runSpec.Env = append(runSpec.Env, env...)
} else {
// Legacy: use ports directly from the spec.

196
internal/agent/edge_rpc.go Normal file
View File

@@ -0,0 +1,196 @@
package agent
import (
"context"
"crypto/x509"
"encoding/pem"
"fmt"
"net"
"os"
"time"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
mcpv1 "git.wntrmute.dev/mc/mcp/gen/mcp/v1"
mcproxy "git.wntrmute.dev/mc/mc-proxy/client/mcproxy"
"git.wntrmute.dev/mc/mcp/internal/registry"
)
// SetupEdgeRoute provisions a TLS cert and registers an mc-proxy route for a
// public hostname. Called by the master on edge nodes.
func (a *Agent) SetupEdgeRoute(ctx context.Context, req *mcpv1.SetupEdgeRouteRequest) (*mcpv1.SetupEdgeRouteResponse, error) {
a.Logger.Info("SetupEdgeRoute", "hostname", req.GetHostname(),
"backend_hostname", req.GetBackendHostname(), "backend_port", req.GetBackendPort())
// Validate required fields.
if req.GetHostname() == "" {
return nil, status.Error(codes.InvalidArgument, "hostname is required")
}
if req.GetBackendHostname() == "" {
return nil, status.Error(codes.InvalidArgument, "backend_hostname is required")
}
if req.GetBackendPort() == 0 {
return nil, status.Error(codes.InvalidArgument, "backend_port is required")
}
if !req.GetBackendTls() {
return nil, status.Error(codes.InvalidArgument, "backend_tls must be true")
}
if a.Proxy == nil {
return nil, status.Error(codes.FailedPrecondition, "mc-proxy not configured")
}
// Resolve the backend hostname to a Tailnet IP.
ips, err := net.LookupHost(req.GetBackendHostname())
if err != nil || len(ips) == 0 {
return nil, status.Errorf(codes.InvalidArgument, "cannot resolve backend_hostname %q: %v", req.GetBackendHostname(), err)
}
backendIP := ips[0]
// Validate the resolved IP is a Tailnet address (100.64.0.0/10).
ip := net.ParseIP(backendIP)
if ip == nil {
return nil, status.Errorf(codes.InvalidArgument, "resolved IP %q is not valid", backendIP)
}
_, tailnet, _ := net.ParseCIDR("100.64.0.0/10")
if !tailnet.Contains(ip) {
return nil, status.Errorf(codes.InvalidArgument, "resolved IP %s is not a Tailnet address", backendIP)
}
backend := fmt.Sprintf("%s:%d", backendIP, req.GetBackendPort())
// Provision TLS cert for the public hostname if cert provisioner is available.
certPath := ""
keyPath := ""
if a.Certs != nil {
if err := a.Certs.EnsureCert(ctx, req.GetHostname(), []string{req.GetHostname()}); err != nil {
return nil, status.Errorf(codes.Internal, "provision cert for %s: %v", req.GetHostname(), err)
}
certPath = a.Proxy.CertPath(req.GetHostname())
keyPath = a.Proxy.KeyPath(req.GetHostname())
} else {
// No cert provisioner — check if certs already exist on disk.
certPath = a.Proxy.CertPath(req.GetHostname())
keyPath = a.Proxy.KeyPath(req.GetHostname())
if _, err := os.Stat(certPath); err != nil {
return nil, status.Errorf(codes.FailedPrecondition, "no cert provisioner and cert not found at %s", certPath)
}
}
// Register the L7 route in mc-proxy.
route := mcproxy.Route{
Hostname: req.GetHostname(),
Backend: backend,
Mode: "l7",
TLSCert: certPath,
TLSKey: keyPath,
BackendTLS: true,
}
if err := a.Proxy.AddRoute(ctx, ":443", route); err != nil {
return nil, status.Errorf(codes.Internal, "add mc-proxy route: %v", err)
}
// Persist the edge route in the registry.
if err := registry.CreateEdgeRoute(a.DB, req.GetHostname(), req.GetBackendHostname(), int(req.GetBackendPort()), certPath, keyPath); err != nil {
a.Logger.Warn("failed to persist edge route", "hostname", req.GetHostname(), "err", err)
}
a.Logger.Info("edge route established",
"hostname", req.GetHostname(), "backend", backend, "cert", certPath)
return &mcpv1.SetupEdgeRouteResponse{}, nil
}
// RemoveEdgeRoute removes an mc-proxy route and cleans up the TLS cert for a
// public hostname. Called by the master on edge nodes.
func (a *Agent) RemoveEdgeRoute(ctx context.Context, req *mcpv1.RemoveEdgeRouteRequest) (*mcpv1.RemoveEdgeRouteResponse, error) {
a.Logger.Info("RemoveEdgeRoute", "hostname", req.GetHostname())
if req.GetHostname() == "" {
return nil, status.Error(codes.InvalidArgument, "hostname is required")
}
if a.Proxy == nil {
return nil, status.Error(codes.FailedPrecondition, "mc-proxy not configured")
}
// Remove the mc-proxy route.
if err := a.Proxy.RemoveRoute(ctx, ":443", req.GetHostname()); err != nil {
a.Logger.Warn("remove mc-proxy route", "hostname", req.GetHostname(), "err", err)
// Continue — clean up cert and registry even if route removal fails.
}
// Remove the TLS cert.
if a.Certs != nil {
if err := a.Certs.RemoveCert(req.GetHostname()); err != nil {
a.Logger.Warn("remove cert", "hostname", req.GetHostname(), "err", err)
}
}
// Remove from registry.
if err := registry.DeleteEdgeRoute(a.DB, req.GetHostname()); err != nil {
a.Logger.Warn("delete edge route from registry", "hostname", req.GetHostname(), "err", err)
}
a.Logger.Info("edge route removed", "hostname", req.GetHostname())
return &mcpv1.RemoveEdgeRouteResponse{}, nil
}
// ListEdgeRoutes returns all edge routes managed by this agent.
func (a *Agent) ListEdgeRoutes(_ context.Context, _ *mcpv1.ListEdgeRoutesRequest) (*mcpv1.ListEdgeRoutesResponse, error) {
a.Logger.Debug("ListEdgeRoutes called")
routes, err := registry.ListEdgeRoutes(a.DB)
if err != nil {
return nil, status.Errorf(codes.Internal, "list edge routes: %v", err)
}
resp := &mcpv1.ListEdgeRoutesResponse{}
for _, r := range routes {
er := &mcpv1.EdgeRoute{
Hostname: r.Hostname,
BackendHostname: r.BackendHostname,
BackendPort: int32(r.BackendPort), //nolint:gosec // port is a small positive integer
}
// Read cert metadata if available.
if r.TLSCert != "" {
if certData, readErr := os.ReadFile(r.TLSCert); readErr == nil { //nolint:gosec // path from registry, not user input
if block, _ := pem.Decode(certData); block != nil {
if cert, parseErr := x509.ParseCertificate(block.Bytes); parseErr == nil {
er.CertSerial = cert.SerialNumber.String()
er.CertExpires = cert.NotAfter.UTC().Format(time.RFC3339)
}
}
}
}
resp.Routes = append(resp.Routes, er)
}
return resp, nil
}
// HealthCheck returns the agent's health status. Called by the master when
// heartbeats are missed.
func (a *Agent) HealthCheck(_ context.Context, _ *mcpv1.HealthCheckRequest) (*mcpv1.HealthCheckResponse, error) {
a.Logger.Debug("HealthCheck called")
st := "healthy"
containers := int32(0)
// Count running containers if the runtime is available.
if a.Runtime != nil {
if list, err := a.Runtime.List(context.Background()); err == nil {
containers = int32(len(list)) //nolint:gosec // container count is small
} else {
st = "degraded"
}
}
return &mcpv1.HealthCheckResponse{
Status: st,
Containers: containers,
}, nil
}

View File

@@ -12,9 +12,9 @@ import (
"google.golang.org/grpc/status"
)
// StopService stops all components of a service.
// StopService stops all components of a service, or a single component if specified.
func (a *Agent) StopService(ctx context.Context, req *mcpv1.StopServiceRequest) (*mcpv1.StopServiceResponse, error) {
a.Logger.Info("StopService", "service", req.GetName())
a.Logger.Info("StopService", "service", req.GetName(), "component", req.GetComponent())
if req.GetName() == "" {
return nil, status.Error(codes.InvalidArgument, "service name is required")
@@ -25,6 +25,13 @@ func (a *Agent) StopService(ctx context.Context, req *mcpv1.StopServiceRequest)
return nil, status.Errorf(codes.Internal, "list components: %v", err)
}
if target := req.GetComponent(); target != "" {
components, err = filterComponents(components, req.GetName(), target)
if err != nil {
return nil, err
}
}
var results []*mcpv1.ComponentResult
for _, c := range components {
containerName := ContainerNameFor(req.GetName(), c.Name)
@@ -59,10 +66,10 @@ func (a *Agent) StopService(ctx context.Context, req *mcpv1.StopServiceRequest)
return &mcpv1.StopServiceResponse{Results: results}, nil
}
// StartService starts all components of a service. If a container already
// exists but is stopped, it is removed first so a fresh one can be created.
// StartService starts all components of a service, or a single component if specified.
// If a container already exists but is stopped, it is removed first so a fresh one can be created.
func (a *Agent) StartService(ctx context.Context, req *mcpv1.StartServiceRequest) (*mcpv1.StartServiceResponse, error) {
a.Logger.Info("StartService", "service", req.GetName())
a.Logger.Info("StartService", "service", req.GetName(), "component", req.GetComponent())
if req.GetName() == "" {
return nil, status.Error(codes.InvalidArgument, "service name is required")
@@ -73,6 +80,13 @@ func (a *Agent) StartService(ctx context.Context, req *mcpv1.StartServiceRequest
return nil, status.Errorf(codes.Internal, "list components: %v", err)
}
if target := req.GetComponent(); target != "" {
components, err = filterComponents(components, req.GetName(), target)
if err != nil {
return nil, err
}
}
var results []*mcpv1.ComponentResult
for _, c := range components {
r := startComponent(ctx, a, req.GetName(), &c)
@@ -82,10 +96,10 @@ func (a *Agent) StartService(ctx context.Context, req *mcpv1.StartServiceRequest
return &mcpv1.StartServiceResponse{Results: results}, nil
}
// RestartService restarts all components of a service by stopping, removing,
// and re-creating each container. The desired_state is not changed.
// RestartService restarts all components of a service, or a single component if specified,
// by stopping, removing, and re-creating each container. The desired_state is not changed.
func (a *Agent) RestartService(ctx context.Context, req *mcpv1.RestartServiceRequest) (*mcpv1.RestartServiceResponse, error) {
a.Logger.Info("RestartService", "service", req.GetName())
a.Logger.Info("RestartService", "service", req.GetName(), "component", req.GetComponent())
if req.GetName() == "" {
return nil, status.Error(codes.InvalidArgument, "service name is required")
@@ -96,6 +110,13 @@ func (a *Agent) RestartService(ctx context.Context, req *mcpv1.RestartServiceReq
return nil, status.Errorf(codes.Internal, "list components: %v", err)
}
if target := req.GetComponent(); target != "" {
components, err = filterComponents(components, req.GetName(), target)
if err != nil {
return nil, err
}
}
var results []*mcpv1.ComponentResult
for _, c := range components {
r := restartComponent(ctx, a, req.GetName(), &c)
@@ -167,6 +188,16 @@ func componentToSpec(service string, c *registry.Component) runtime.ContainerSpe
}
}
// filterComponents returns only the component matching target, or an error if not found.
func filterComponents(components []registry.Component, service, target string) ([]registry.Component, error) {
for _, c := range components {
if c.Name == target {
return []registry.Component{c}, nil
}
}
return nil, status.Errorf(codes.NotFound, "component %q not found in service %q", target, service)
}
// componentExists checks whether a component already exists in the registry.
func componentExists(db *sql.DB, service, name string) bool {
_, err := registry.GetComponent(db, service, name)

View File

@@ -48,6 +48,16 @@ func (p *ProxyRouter) Close() error {
return p.client.Close()
}
// CertPath returns the expected TLS certificate path for a given name.
func (p *ProxyRouter) CertPath(name string) string {
return filepath.Join(p.certDir, name+".pem")
}
// KeyPath returns the expected TLS key path for a given name.
func (p *ProxyRouter) KeyPath(name string) string {
return filepath.Join(p.certDir, name+".key")
}
// GetStatus returns the mc-proxy server status.
func (p *ProxyRouter) GetStatus(ctx context.Context) (*mcproxy.Status, error) {
if p == nil {

View File

@@ -69,6 +69,8 @@ func (a *Agent) AddProxyRoute(ctx context.Context, req *mcpv1.AddProxyRouteReque
Backend: req.GetBackend(),
Mode: req.GetMode(),
BackendTLS: req.GetBackendTls(),
TLSCert: req.GetTlsCert(),
TLSKey: req.GetTlsKey(),
}
if err := a.Proxy.AddRoute(ctx, req.GetListenerAddr(), route); err != nil {

View File

@@ -142,4 +142,18 @@ var migrations = []string{
FOREIGN KEY (service, component) REFERENCES components(service, name) ON DELETE CASCADE
);
`,
// Migration 3: service comment
`ALTER TABLE services ADD COLUMN comment TEXT NOT NULL DEFAULT '';`,
// Migration 4: edge routes (v2 — public routes managed by the master)
`CREATE TABLE IF NOT EXISTS edge_routes (
hostname TEXT NOT NULL PRIMARY KEY,
backend_hostname TEXT NOT NULL,
backend_port INTEGER NOT NULL,
tls_cert TEXT NOT NULL DEFAULT '',
tls_key TEXT NOT NULL DEFAULT '',
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
);`,
}

View File

@@ -0,0 +1,93 @@
package registry
import (
"database/sql"
"fmt"
"time"
)
// EdgeRoute represents a public edge route managed by the master.
type EdgeRoute struct {
Hostname string
BackendHostname string
BackendPort int
TLSCert string
TLSKey string
CreatedAt time.Time
UpdatedAt time.Time
}
// CreateEdgeRoute inserts or replaces an edge route.
func CreateEdgeRoute(db *sql.DB, hostname, backendHostname string, backendPort int, tlsCert, tlsKey string) error {
_, err := db.Exec(`
INSERT INTO edge_routes (hostname, backend_hostname, backend_port, tls_cert, tls_key, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, datetime('now'), datetime('now'))
ON CONFLICT(hostname) DO UPDATE SET
backend_hostname = excluded.backend_hostname,
backend_port = excluded.backend_port,
tls_cert = excluded.tls_cert,
tls_key = excluded.tls_key,
updated_at = datetime('now')
`, hostname, backendHostname, backendPort, tlsCert, tlsKey)
if err != nil {
return fmt.Errorf("create edge route %s: %w", hostname, err)
}
return nil
}
// GetEdgeRoute returns a single edge route by hostname.
func GetEdgeRoute(db *sql.DB, hostname string) (*EdgeRoute, error) {
var r EdgeRoute
var createdAt, updatedAt string
err := db.QueryRow(`
SELECT hostname, backend_hostname, backend_port, tls_cert, tls_key, created_at, updated_at
FROM edge_routes WHERE hostname = ?
`, hostname).Scan(&r.Hostname, &r.BackendHostname, &r.BackendPort, &r.TLSCert, &r.TLSKey, &createdAt, &updatedAt)
if err == sql.ErrNoRows {
return nil, nil
}
if err != nil {
return nil, fmt.Errorf("get edge route %s: %w", hostname, err)
}
r.CreatedAt, _ = time.Parse("2006-01-02 15:04:05", createdAt)
r.UpdatedAt, _ = time.Parse("2006-01-02 15:04:05", updatedAt)
return &r, nil
}
// ListEdgeRoutes returns all edge routes.
func ListEdgeRoutes(db *sql.DB) ([]*EdgeRoute, error) {
rows, err := db.Query(`
SELECT hostname, backend_hostname, backend_port, tls_cert, tls_key, created_at, updated_at
FROM edge_routes ORDER BY hostname
`)
if err != nil {
return nil, fmt.Errorf("list edge routes: %w", err)
}
defer func() { _ = rows.Close() }()
var routes []*EdgeRoute
for rows.Next() {
var r EdgeRoute
var createdAt, updatedAt string
if err := rows.Scan(&r.Hostname, &r.BackendHostname, &r.BackendPort, &r.TLSCert, &r.TLSKey, &createdAt, &updatedAt); err != nil {
return nil, fmt.Errorf("scan edge route: %w", err)
}
r.CreatedAt, _ = time.Parse("2006-01-02 15:04:05", createdAt)
r.UpdatedAt, _ = time.Parse("2006-01-02 15:04:05", updatedAt)
routes = append(routes, &r)
}
return routes, rows.Err()
}
// DeleteEdgeRoute removes an edge route by hostname.
func DeleteEdgeRoute(db *sql.DB, hostname string) error {
result, err := db.Exec(`DELETE FROM edge_routes WHERE hostname = ?`, hostname)
if err != nil {
return fmt.Errorf("delete edge route %s: %w", hostname, err)
}
n, _ := result.RowsAffected()
if n == 0 {
return fmt.Errorf("edge route %s not found", hostname)
}
return nil
}

View File

@@ -42,6 +42,14 @@ service McpAgentService {
rpc AddProxyRoute(AddProxyRouteRequest) returns (AddProxyRouteResponse);
rpc RemoveProxyRoute(RemoveProxyRouteRequest) returns (RemoveProxyRouteResponse);
// Edge routing (called by master on edge nodes)
rpc SetupEdgeRoute(SetupEdgeRouteRequest) returns (SetupEdgeRouteResponse);
rpc RemoveEdgeRoute(RemoveEdgeRouteRequest) returns (RemoveEdgeRouteResponse);
rpc ListEdgeRoutes(ListEdgeRoutesRequest) returns (ListEdgeRoutesResponse);
// Health (called by master on missed heartbeats)
rpc HealthCheck(HealthCheckRequest) returns (HealthCheckResponse);
// Logs
rpc Logs(LogsRequest) returns (stream LogsResponse);
}
@@ -72,6 +80,7 @@ message ServiceSpec {
string name = 1;
bool active = 2;
repeated ComponentSpec components = 3;
string comment = 4;
}
message DeployRequest {
@@ -92,6 +101,7 @@ message ComponentResult {
message StopServiceRequest {
string name = 1;
string component = 2;
}
message StopServiceResponse {
@@ -100,6 +110,7 @@ message StopServiceResponse {
message StartServiceRequest {
string name = 1;
string component = 2;
}
message StartServiceResponse {
@@ -108,6 +119,7 @@ message StartServiceResponse {
message RestartServiceRequest {
string name = 1;
string component = 2;
}
message RestartServiceResponse {
@@ -148,6 +160,7 @@ message ServiceInfo {
string name = 1;
bool active = 2;
repeated ComponentInfo components = 3;
string comment = 4;
}
message ComponentInfo {
@@ -362,6 +375,8 @@ message AddProxyRouteRequest {
string backend = 3;
string mode = 4; // "l4" or "l7"
bool backend_tls = 5;
string tls_cert = 6; // path to TLS cert (required for l7)
string tls_key = 7; // path to TLS key (required for l7)
}
message AddProxyRouteResponse {}
@@ -372,3 +387,43 @@ message RemoveProxyRouteRequest {
}
message RemoveProxyRouteResponse {}
// --- Edge routes (v2) ---
message SetupEdgeRouteRequest {
string hostname = 1; // public hostname (e.g. "mcq.metacircular.net")
string backend_hostname = 2; // internal .svc.mcp hostname
int32 backend_port = 3; // port on worker's mc-proxy
bool backend_tls = 4; // MUST be true; agent rejects false
}
message SetupEdgeRouteResponse {}
message RemoveEdgeRouteRequest {
string hostname = 1;
}
message RemoveEdgeRouteResponse {}
message ListEdgeRoutesRequest {}
message ListEdgeRoutesResponse {
repeated EdgeRoute routes = 1;
}
message EdgeRoute {
string hostname = 1;
string backend_hostname = 2;
int32 backend_port = 3;
string cert_serial = 4;
string cert_expires = 5; // RFC3339
}
// --- Health check (v2) ---
message HealthCheckRequest {}
message HealthCheckResponse {
string status = 1; // "healthy" or "degraded"
int32 containers = 2;
}