4 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
11 changed files with 1174 additions and 593 deletions

View File

@@ -1,502 +0,0 @@
# MCP v2 -- Multi-Node Control Plane
## Overview
MCP v2 introduces multi-node orchestration with a master/agent topology.
The CLI no longer dials agents directly. A dedicated **mcp-master** daemon
coordinates deployments across nodes, handles cross-node concerns (edge
routing, certificate provisioning, DNS), and serves as the single control
point for the platform.
### Motivation
v1 deployed successfully on a single node (rift) but exposed operational
pain points as services needed public-facing routes through svc:
- **Manual edge routing**: Exposing mcq.metacircular.net required hand-editing
mc-proxy's TOML config on svc, provisioning a TLS cert manually, updating
the SQLite database when the config and database diverged, and debugging
silent failures. Every redeployment risked breaking the public route.
- **Dynamic port instability**: The route system assigns ephemeral host ports
that change on every deploy. svc's mc-proxy pointed at a specific port
(e.g., `100.95.252.120:48080`), which went stale after redeployment.
Container ports are also localhost-only under rootless podman, requiring
explicit Tailscale IP bindings for external access.
- **$PORT env override conflict**: The mcdsl config loader overrides
`listen_addr` from `$PORT` when routes are present. This meant containers
ignored their configured port and listened on the route-allocated one
instead, breaking explicit port mappings that expected the config port.
- **Cert chain issues**: mc-proxy requires full certificate chains (leaf +
intermediates). Certs provisioned outside the standard metacrypt flow
were leaf-only and caused silent TLS handshake failures (`client_bytes=7
backend_bytes=0` with no error logged).
- **mc-proxy database divergence**: mc-proxy persists routes in SQLite.
Routes added via the admin API override the TOML config. Editing the TOML
alone had no effect until the database was manually updated -- a failure
mode that took hours to diagnose.
- **No cross-node coordination**: The v1 CLI talks directly to individual
agents. There is no mechanism for one agent to tell another "set up a
route for this service." Every cross-node operation was manual.
v2 addresses all of these by making the master the single coordination
point for deployments, with agents handling local concerns (containers,
mc-proxy routes, cert provisioning) on instruction from the master.
### What Changes from v1
| Concern | v1 | v2 |
|---------|----|----|
| CLI target | CLI dials agents directly | CLI dials the master |
| Node awareness | CLI routes by `node` field in service defs | Master owns the node registry |
| Service definitions | Live on operator workstation | Pushed to master, which distributes to agents |
| Edge routing | Manual mc-proxy config on svc | Master coordinates agent-to-agent setup |
| Cert provisioning | Agent provisions for local mc-proxy only | Any agent can provision certs (edge included) |
| DNS registration | Agent registers records on deploy | Master coordinates DNS across zones |
### What Stays the Same
The agent's core responsibilities are unchanged: it manages containers via
podman, stores its local registry in SQLite, monitors for drift, and alerts
the operator. The agent gains new RPCs for edge routing but does not become
aware of other nodes -- the master handles all cross-node coordination.
---
## Topology
```
Operator workstation (vade)
┌──────────────────────────┐
│ mcp (CLI) │
│ │
│ gRPC ───────────────────┼─── overlay ───┐
└──────────────────────────┘ │
Master node (straylight)
┌──────────────────────────────────────────────────────┐
│ mcp-master │
│ ├── node registry (all nodes, roles, addresses) │
│ ├── service definitions (pushed from CLI) │
│ └── deployment coordinator │
│ │
│ mcp-agent │
│ ├── mcns container │
│ ├── metacrypt container │
│ ├── mcr container │
│ └── mc-proxy (straylight) │
└──────────┬──────────────────────────┬────────────────┘
│ │
overlay overlay
│ │
▼ ▼
Worker node (rift) Edge node (svc)
┌─────────────────────┐ ┌─────────────────────────┐
│ mcp-agent │ │ mcp-agent │
│ ├── exo │ │ ├── mc-proxy (svc) │
│ ├── mcq │ │ └── (edge routes only) │
│ ├── mcdoc │ │ │
│ ├── sgard │ │ Edge routes: │
│ ├── kls │ │ mcq.metacircular.net │
│ └── mc-proxy │ │ mcdoc.metacircular.net │
│ (rift) │ │ exo.metacircular.net │
└─────────────────────┘ │ sgard.metacircular.net │
└─────────────────────────┘
```
### Node Roles
| Role | Purpose | Nodes |
|------|---------|-------|
| **master** | Runs mcp-master + mcp-agent. Hosts core infrastructure (mcns, metacrypt, mcr). Single coordination point. | straylight |
| **worker** | Runs mcp-agent. Hosts application services. | rift |
| **edge** | Runs mcp-agent. Terminates public TLS, forwards to internal services. No application containers. | svc |
Every node runs an mcp-agent. The master node also runs mcp-master.
The master's local agent manages the infrastructure services (mcns,
metacrypt, mcr) the same way rift's agent manages application services.
### mc-proxy Mesh
Each node runs its own mc-proxy instance. They form a routing mesh:
```
mc-proxy (straylight)
├── :443 L7 routes for metacrypt-web, mcr-web
├── :8443 L4 passthrough for metacrypt-api, mcr-api
└── :9443 L4 passthrough for gRPC services
mc-proxy (rift)
├── :443 L7 routes for internal .svc.mcp hostnames
└── :8443 L4/L7 routes for internal APIs
mc-proxy (svc)
└── :443 L7 termination for public hostnames
→ forwards to internal .svc.mcp endpoints
```
---
## mcp-master
The master is a new binary that coordinates cross-node operations. It is
**not** a replacement for the agent -- it sits above agents and orchestrates
them.
### Responsibilities
1. **Accept CLI commands** via gRPC (deploy, undeploy, status, sync).
2. **Route deployments** to the correct agent based on the service
definition's `node` field.
3. **Detect public hostnames** in service definitions and coordinate edge
routing with the edge node's agent.
4. **Validate public hostnames** against a configured allowlist of domains
(e.g., `metacircular.net`, `wntrmute.net`).
5. **Resolve edge nodes** by checking DNS CNAME records to determine which
node handles public traffic for a given hostname.
6. **Coordinate undeploy** across nodes: tear down the service on the
worker, then clean up edge routes on the edge node.
### What the Master Does NOT Do
- Store container state (agents own their registries).
- Manage container lifecycle directly (agents do this).
- Run containers (the co-located agent does).
- Replace the agent on any node.
### Master Configuration
```toml
[server]
grpc_addr = "100.x.x.x:9555" # master listens on overlay
tls_cert = "/srv/mcp-master/certs/cert.pem"
tls_key = "/srv/mcp-master/certs/key.pem"
[mcias]
server_url = "https://mcias.metacircular.net:8443"
service_name = "mcp-master"
# Allowed public domains. Hostnames in service definitions must fall
# under one of these suffixes.
[edge]
allowed_domains = ["metacircular.net", "wntrmute.net"]
# Node registry. The master knows about all nodes.
[[nodes]]
name = "straylight"
address = "100.x.x.x:9444"
role = "master"
[[nodes]]
name = "rift"
address = "100.95.252.120:9444"
role = "worker"
[[nodes]]
name = "svc"
address = "100.x.x.x:9444"
role = "edge"
```
---
## Edge Routing
The core v2 feature: when a service declares a public hostname, the
master automatically provisions the edge route.
### Service Definition
Public hostnames are declared in the route's `hostname` field. The
master distinguishes public from internal hostnames by checking whether
they fall under a `.svc.mcp.` subdomain:
- `mcq.svc.mcp.metacircular.net` → internal (handled by local mc-proxy)
- `mcq.metacircular.net` → public (requires edge routing)
```toml
name = "mcq"
node = "rift"
active = true
[[components]]
name = "mcq"
image = "mcr.svc.mcp.metacircular.net:8443/mcq:v0.4.0"
volumes = ["/srv/mcq:/srv/mcq"]
cmd = ["server", "--config", "/srv/mcq/mcq.toml"]
# Internal route: handled by rift's mc-proxy.
[[components.routes]]
name = "internal"
port = 8443
mode = "l7"
# Public route: master detects this and sets up edge routing on svc.
[[components.routes]]
name = "public"
port = 8443
mode = "l7"
hostname = "mcq.metacircular.net"
```
### Deploy Flow with Edge Routing
When the master receives `Deploy(mcq)`:
1. **Route to worker**: Master sends `Deploy` RPC to rift's agent with
the full service spec. Rift's agent deploys the container and
registers mc-proxy routes for all hostnames (both internal and public)
on its local mc-proxy.
2. **Detect public hostnames**: Master inspects the service spec for
hostnames that are not `.svc.mcp.` subdomains.
3. **Validate domains**: Master checks that `mcq.metacircular.net` falls
under an allowed domain (`metacircular.net` ✓).
4. **Resolve edge node**: Master performs a DNS lookup for
`mcq.metacircular.net`. If it's a CNAME to `svc.metacircular.net`,
the master resolves `svc.metacircular.net` to identify the edge node
as `svc`. If DNS is not yet configured (no CNAME), the master uses
the default edge node from config.
5. **Set up edge route**: Master sends an `SetupEdgeRoute` RPC to svc's
agent:
```
SetupEdgeRoute(
hostname: "mcq.metacircular.net"
backend_hostname: "mcq.svc.mcp.metacircular.net"
backend_port: 8443
)
```
6. **Svc agent provisions**: On receiving `SetupEdgeRoute`, svc's agent:
a. Provisions a TLS certificate from Metacrypt for
`mcq.metacircular.net`.
b. Registers an L7 route in its local mc-proxy:
`mcq.metacircular.net:443 → mcq.svc.mcp.metacircular.net:8443`
with the provisioned cert.
7. **Master records the edge route** in its own registry for undeploy
cleanup.
### Undeploy Flow
When the master receives `Undeploy(mcq)`:
1. **Look up edge routes**: Master checks its registry for edge routes
associated with mcq.
2. **Remove edge route**: Master sends `RemoveEdgeRoute(mcq.metacircular.net)`
to svc's agent. Svc's agent removes the mc-proxy route and cleans up
the cert.
3. **Undeploy on worker**: Master sends `Undeploy` RPC to rift's agent.
Rift's agent tears down the container, routes, DNS, and certs as in v1.
### Edge Node DNS Resolution
The master determines which edge node handles a public hostname by
checking DNS:
1. Look up `mcq.metacircular.net` → CNAME `svc.metacircular.net`
2. Look up `svc.metacircular.net` → IP address
3. Match the IP against known edge nodes
If no CNAME exists yet (operator hasn't set it up), the master warns but
does not fail. The operator sets up DNS manually at Hurricane Electric.
The master can provide a `mcp dns check` command that verifies all public
hostnames resolve correctly.
---
## Agent Changes for v2
### New RPCs
```protobuf
// Edge routing -- called by master on edge nodes.
rpc SetupEdgeRoute(SetupEdgeRouteRequest) returns (SetupEdgeRouteResponse);
rpc RemoveEdgeRoute(RemoveEdgeRouteRequest) returns (RemoveEdgeRouteResponse);
rpc ListEdgeRoutes(ListEdgeRoutesRequest) returns (ListEdgeRoutesResponse);
message SetupEdgeRouteRequest {
string hostname = 1; // public hostname (e.g. "mcq.metacircular.net")
string backend_hostname = 2; // internal hostname (e.g. "mcq.svc.mcp.metacircular.net")
int32 backend_port = 3; // port on the worker's mc-proxy (e.g. 8443)
}
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;
}
```
### SetupEdgeRoute Implementation
When the agent receives `SetupEdgeRoute`:
1. **Resolve backend address**: The agent resolves `backend_hostname` to
an IP address (the worker node's overlay IP). It uses the port from
the request to form the backend address (e.g., `100.95.252.120:8443`).
2. **Provision TLS cert**: The agent calls Metacrypt's CA API to issue a
certificate for the public hostname. The cert and key are written to
the mc-proxy cert directory.
3. **Register mc-proxy route**: The agent adds an L7 route to its local
mc-proxy:
- Listener: `:443`
- Hostname: `mcq.metacircular.net`
- Backend: `100.95.252.120:8443`
- Mode: `l7`
- TLS cert/key: the provisioned cert
- Backend TLS: `true` (worker's mc-proxy serves TLS)
4. **Record the edge route** in the agent's local registry for listing
and cleanup.
### Cert Provisioning on All Agents
All agents need Metacrypt configuration to provision certs:
```toml
[metacrypt]
server_url = "https://metacrypt.svc.mcp.metacircular.net:8443"
ca_cert = "/srv/mcp/certs/metacircular-ca.pem"
mount = "pki"
issuer = "infra"
token_path = "/srv/mcp/metacrypt-token"
```
The svc agent provisions certs for public hostnames. The rift agent
provisions certs for internal hostnames. Both use the same Metacrypt API.
---
## CLI Changes for v2
The CLI's `[[nodes]]` config is replaced by a single master address:
```toml
[master]
address = "100.x.x.x:9555"
[mcias]
server_url = "https://mcias.metacircular.net:8443"
service_name = "mcp"
[auth]
token_path = "/home/kyle/.config/mcp/token"
[services]
dir = "/home/kyle/.config/mcp/services"
```
Commands that currently iterate over nodes (`mcp ps`, `mcp list`,
`mcp node list`) instead query the master, which aggregates from all
agents.
Service definition files remain on the operator's workstation. The CLI
pushes them to the master on `mcp deploy` and `mcp sync`.
---
## Migration Plan
### Phase 1: Deploy mcp-agent on svc
svc currently has no mcp-agent. Install and configure one:
- Create `mcp` user on svc (Debian: `install-agent.sh`)
- Install mcp-agent binary
- Configure with Metacrypt and mc-proxy socket access
- Verify with `mcp node list` (svc shows up)
### Phase 2: Add edge routing RPCs to agents
Implement `SetupEdgeRoute`, `RemoveEdgeRoute`, `ListEdgeRoutes` on the
agent. Test by calling them directly from the CLI before the master exists.
### Phase 3: Build mcp-master
Start with the core coordination loop:
1. Accept `Deploy` from CLI
2. Forward to the correct agent
3. Detect public hostnames
4. Call `SetupEdgeRoute` on the edge agent
### Phase 4: Provision straylight
New node (straylight) takes over as master and hosts core infrastructure:
1. Deploy mcp-agent on straylight
2. Migrate mcns, metacrypt, mcr from rift to straylight
3. Deploy mcp-master on straylight
4. Update CLI config to point at master
### Phase 5: Cut over
- Update DNS to point `*.svc.mcp.metacircular.net` at straylight
- Update service definitions to use new node assignments
- Verify all services via `mcp ps` and public endpoint tests
---
## Open Questions
1. **Master HA**: mcp-master is a single point of failure. For v2, this
is acceptable (operator can SSH to agents directly if master is down).
v3 could add master replication or make agents self-sufficient for
local operations when the master is unreachable.
2. **Service placement**: v2 still requires explicit `node` assignment
in service definitions. Automatic placement based on resource
availability is a future concern.
3. **Cert renewal on edge**: Edge certs have a 90-day TTL. The edge
agent needs a renewal loop (similar to the existing `renewWindow`
check in `EnsureCert`) or the master needs to periodically re-check
edge routes.
4. **mc-proxy database vs config**: mc-proxy persists routes in SQLite,
which can diverge from the TOML config. The agent should be the sole
manager of mc-proxy routes via the gRPC admin API, not the TOML file.
This avoids the stale-database problem encountered during v1
operations on svc.
5. **straylight hardware**: What hardware is straylight? Does it run
NixOS or Debian? Does it use rootless podman like rift?
6. **Mono-repo for core infrastructure**: The current layout has each
service as a separate git repo under `~/src/metacircular/`. A
mono-repo for core infrastructure (mcp, mcp-master, mcns, metacrypt,
mcr, mc-proxy, mcdsl) would simplify coordinated changes (e.g., a
proto change that touches agent + CLI + mc-proxy client), eliminate
the `uses_mcdsl` build flag / vendoring, enable a single CI pipeline,
and allow atomic platform versioning (one tag per release). Non-core
application services (exo, mcq, mcdoc, sgard, kls, mcat) would
remain as separate repos with independent release cadences. This is
a large migration best tackled after straylight is running and the
master exists, when the build/deploy pipeline is already being
reorganized.

View File

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

View File

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

View File

@@ -211,6 +211,7 @@ type ServiceSpec struct {
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
Active bool `protobuf:"varint,2,opt,name=active,proto3" json:"active,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"` 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 unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
} }
@@ -266,6 +267,13 @@ func (x *ServiceSpec) GetComponents() []*ComponentSpec {
return nil return nil
} }
func (x *ServiceSpec) GetComment() string {
if x != nil {
return x.Comment
}
return ""
}
type DeployRequest struct { type DeployRequest struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
Service *ServiceSpec `protobuf:"bytes,1,opt,name=service,proto3" json:"service,omitempty"` Service *ServiceSpec `protobuf:"bytes,1,opt,name=service,proto3" json:"service,omitempty"`
@@ -426,6 +434,7 @@ func (x *ComponentResult) GetError() string {
type StopServiceRequest struct { type StopServiceRequest struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` 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 unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
} }
@@ -467,6 +476,13 @@ func (x *StopServiceRequest) GetName() string {
return "" return ""
} }
func (x *StopServiceRequest) GetComponent() string {
if x != nil {
return x.Component
}
return ""
}
type StopServiceResponse struct { type StopServiceResponse struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
Results []*ComponentResult `protobuf:"bytes,1,rep,name=results,proto3" json:"results,omitempty"` Results []*ComponentResult `protobuf:"bytes,1,rep,name=results,proto3" json:"results,omitempty"`
@@ -514,6 +530,7 @@ func (x *StopServiceResponse) GetResults() []*ComponentResult {
type StartServiceRequest struct { type StartServiceRequest struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` 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 unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
} }
@@ -555,6 +572,13 @@ func (x *StartServiceRequest) GetName() string {
return "" return ""
} }
func (x *StartServiceRequest) GetComponent() string {
if x != nil {
return x.Component
}
return ""
}
type StartServiceResponse struct { type StartServiceResponse struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
Results []*ComponentResult `protobuf:"bytes,1,rep,name=results,proto3" json:"results,omitempty"` Results []*ComponentResult `protobuf:"bytes,1,rep,name=results,proto3" json:"results,omitempty"`
@@ -602,6 +626,7 @@ func (x *StartServiceResponse) GetResults() []*ComponentResult {
type RestartServiceRequest struct { type RestartServiceRequest struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` 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 unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
} }
@@ -643,6 +668,13 @@ func (x *RestartServiceRequest) GetName() string {
return "" return ""
} }
func (x *RestartServiceRequest) GetComponent() string {
if x != nil {
return x.Component
}
return ""
}
type RestartServiceResponse struct { type RestartServiceResponse struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
Results []*ComponentResult `protobuf:"bytes,1,rep,name=results,proto3" json:"results,omitempty"` 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"` Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
Active bool `protobuf:"varint,2,opt,name=active,proto3" json:"active,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"` 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 unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
} }
@@ -1021,6 +1054,13 @@ func (x *ServiceInfo) GetComponents() []*ComponentInfo {
return nil return nil
} }
func (x *ServiceInfo) GetComment() string {
if x != nil {
return x.Comment
}
return ""
}
type ComponentInfo struct { type ComponentInfo struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
@@ -3024,6 +3064,434 @@ func (*RemoveProxyRouteResponse) Descriptor() ([]byte, []int) {
return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{52} 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 var File_proto_mcp_v1_mcp_proto protoreflect.FileDescriptor
const file_proto_mcp_v1_mcp_proto_rawDesc = "" + const file_proto_mcp_v1_mcp_proto_rawDesc = "" +
@@ -3045,13 +3513,14 @@ const file_proto_mcp_v1_mcp_proto_rawDesc = "" +
"\x03cmd\x18\b \x03(\tR\x03cmd\x12)\n" + "\x03cmd\x18\b \x03(\tR\x03cmd\x12)\n" +
"\x06routes\x18\t \x03(\v2\x11.mcp.v1.RouteSpecR\x06routes\x12\x10\n" + "\x06routes\x18\t \x03(\v2\x11.mcp.v1.RouteSpecR\x06routes\x12\x10\n" +
"\x03env\x18\n" + "\x03env\x18\n" +
" \x03(\tR\x03env\"p\n" + " \x03(\tR\x03env\"\x8a\x01\n" +
"\vServiceSpec\x12\x12\n" + "\vServiceSpec\x12\x12\n" +
"\x04name\x18\x01 \x01(\tR\x04name\x12\x16\n" + "\x04name\x18\x01 \x01(\tR\x04name\x12\x16\n" +
"\x06active\x18\x02 \x01(\bR\x06active\x125\n" + "\x06active\x18\x02 \x01(\bR\x06active\x125\n" +
"\n" + "\n" +
"components\x18\x03 \x03(\v2\x15.mcp.v1.ComponentSpecR\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" + "\rDeployRequest\x12-\n" +
"\aservice\x18\x01 \x01(\v2\x13.mcp.v1.ServiceSpecR\aservice\x12\x1c\n" + "\aservice\x18\x01 \x01(\v2\x13.mcp.v1.ServiceSpecR\aservice\x12\x1c\n" +
"\tcomponent\x18\x02 \x01(\tR\tcomponent\"C\n" + "\tcomponent\x18\x02 \x01(\tR\tcomponent\"C\n" +
@@ -3060,17 +3529,20 @@ const file_proto_mcp_v1_mcp_proto_rawDesc = "" +
"\x0fComponentResult\x12\x12\n" + "\x0fComponentResult\x12\x12\n" +
"\x04name\x18\x01 \x01(\tR\x04name\x12\x18\n" + "\x04name\x18\x01 \x01(\tR\x04name\x12\x18\n" +
"\asuccess\x18\x02 \x01(\bR\asuccess\x12\x14\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" + "\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" + "\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" + "\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" + "\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" + "\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" + "\x16RestartServiceResponse\x121\n" +
"\aresults\x18\x01 \x03(\v2\x17.mcp.v1.ComponentResultR\aresults\",\n" + "\aresults\x18\x01 \x03(\v2\x17.mcp.v1.ComponentResultR\aresults\",\n" +
"\x16UndeployServiceRequest\x12\x12\n" + "\x16UndeployServiceRequest\x12\x12\n" +
@@ -3085,13 +3557,14 @@ const file_proto_mcp_v1_mcp_proto_rawDesc = "" +
"\x04name\x18\x01 \x01(\tR\x04name\x12\x18\n" + "\x04name\x18\x01 \x01(\tR\x04name\x12\x18\n" +
"\achanged\x18\x02 \x01(\bR\achanged\x12\x18\n" + "\achanged\x18\x02 \x01(\bR\achanged\x12\x18\n" +
"\asummary\x18\x03 \x01(\tR\asummary\"\x15\n" + "\asummary\x18\x03 \x01(\tR\asummary\"\x15\n" +
"\x13ListServicesRequest\"p\n" + "\x13ListServicesRequest\"\x8a\x01\n" +
"\vServiceInfo\x12\x12\n" + "\vServiceInfo\x12\x12\n" +
"\x04name\x18\x01 \x01(\tR\x04name\x12\x16\n" + "\x04name\x18\x01 \x01(\tR\x04name\x12\x16\n" +
"\x06active\x18\x02 \x01(\bR\x06active\x125\n" + "\x06active\x18\x02 \x01(\bR\x06active\x125\n" +
"\n" + "\n" +
"components\x18\x03 \x03(\v2\x15.mcp.v1.ComponentInfoR\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" + "\rComponentInfo\x12\x12\n" +
"\x04name\x18\x01 \x01(\tR\x04name\x12\x14\n" + "\x04name\x18\x01 \x01(\tR\x04name\x12\x14\n" +
"\x05image\x18\x02 \x01(\tR\x05image\x12#\n" + "\x05image\x18\x02 \x01(\tR\x05image\x12#\n" +
@@ -3228,7 +3701,33 @@ const file_proto_mcp_v1_mcp_proto_rawDesc = "" +
"\x17RemoveProxyRouteRequest\x12#\n" + "\x17RemoveProxyRouteRequest\x12#\n" +
"\rlistener_addr\x18\x01 \x01(\tR\flistenerAddr\x12\x1a\n" + "\rlistener_addr\x18\x01 \x01(\tR\flistenerAddr\x12\x1a\n" +
"\bhostname\x18\x02 \x01(\tR\bhostname\"\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" + "\x0fMcpAgentService\x127\n" +
"\x06Deploy\x12\x15.mcp.v1.DeployRequest\x1a\x16.mcp.v1.DeployResponse\x12R\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" + "\x0fUndeployService\x12\x1e.mcp.v1.UndeployServiceRequest\x1a\x1f.mcp.v1.UndeployServiceResponse\x12F\n" +
@@ -3248,7 +3747,11 @@ const file_proto_mcp_v1_mcp_proto_rawDesc = "" +
"\x0eListDNSRecords\x12\x1d.mcp.v1.ListDNSRecordsRequest\x1a\x1e.mcp.v1.ListDNSRecordsResponse\x12R\n" + "\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" + "\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" + "\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" "\x04Logs\x12\x13.mcp.v1.LogsRequest\x1a\x14.mcp.v1.LogsResponse0\x01B*Z(git.wntrmute.dev/mc/mcp/gen/mcp/v1;mcpv1b\x06proto3"
var ( var (
@@ -3263,7 +3766,7 @@ func file_proto_mcp_v1_mcp_proto_rawDescGZIP() []byte {
return file_proto_mcp_v1_mcp_proto_rawDescData 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{ var file_proto_mcp_v1_mcp_proto_goTypes = []any{
(*RouteSpec)(nil), // 0: mcp.v1.RouteSpec (*RouteSpec)(nil), // 0: mcp.v1.RouteSpec
(*ComponentSpec)(nil), // 1: mcp.v1.ComponentSpec (*ComponentSpec)(nil), // 1: mcp.v1.ComponentSpec
@@ -3318,7 +3821,16 @@ var file_proto_mcp_v1_mcp_proto_goTypes = []any{
(*AddProxyRouteResponse)(nil), // 50: mcp.v1.AddProxyRouteResponse (*AddProxyRouteResponse)(nil), // 50: mcp.v1.AddProxyRouteResponse
(*RemoveProxyRouteRequest)(nil), // 51: mcp.v1.RemoveProxyRouteRequest (*RemoveProxyRouteRequest)(nil), // 51: mcp.v1.RemoveProxyRouteRequest
(*RemoveProxyRouteResponse)(nil), // 52: mcp.v1.RemoveProxyRouteResponse (*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{ var file_proto_mcp_v1_mcp_proto_depIdxs = []int32{
0, // 0: mcp.v1.ComponentSpec.routes:type_name -> mcp.v1.RouteSpec 0, // 0: mcp.v1.ComponentSpec.routes:type_name -> mcp.v1.RouteSpec
@@ -3332,64 +3844,73 @@ var file_proto_mcp_v1_mcp_proto_depIdxs = []int32{
2, // 8: mcp.v1.SyncDesiredStateRequest.services:type_name -> mcp.v1.ServiceSpec 2, // 8: mcp.v1.SyncDesiredStateRequest.services:type_name -> mcp.v1.ServiceSpec
16, // 9: mcp.v1.SyncDesiredStateResponse.results:type_name -> mcp.v1.ServiceSyncResult 16, // 9: mcp.v1.SyncDesiredStateResponse.results:type_name -> mcp.v1.ServiceSyncResult
19, // 10: mcp.v1.ServiceInfo.components:type_name -> mcp.v1.ComponentInfo 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 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 18, // 14: mcp.v1.GetServiceStatusResponse.services:type_name -> mcp.v1.ServiceInfo
22, // 15: mcp.v1.GetServiceStatusResponse.drift:type_name -> mcp.v1.DriftInfo 22, // 15: mcp.v1.GetServiceStatusResponse.drift:type_name -> mcp.v1.DriftInfo
23, // 16: mcp.v1.GetServiceStatusResponse.recent_events:type_name -> mcp.v1.EventInfo 23, // 16: mcp.v1.GetServiceStatusResponse.recent_events:type_name -> mcp.v1.EventInfo
18, // 17: mcp.v1.LiveCheckResponse.services:type_name -> mcp.v1.ServiceInfo 18, // 17: mcp.v1.LiveCheckResponse.services:type_name -> mcp.v1.ServiceInfo
28, // 18: mcp.v1.AdoptContainersResponse.results:type_name -> mcp.v1.AdoptResult 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 38, // 20: mcp.v1.PurgeResponse.results:type_name -> mcp.v1.PurgeResult
43, // 21: mcp.v1.DNSZone.records:type_name -> mcp.v1.DNSRecord 43, // 21: mcp.v1.DNSZone.records:type_name -> mcp.v1.DNSRecord
42, // 22: mcp.v1.ListDNSRecordsResponse.zones:type_name -> mcp.v1.DNSZone 42, // 22: mcp.v1.ListDNSRecordsResponse.zones:type_name -> mcp.v1.DNSZone
46, // 23: mcp.v1.ProxyListenerInfo.routes:type_name -> mcp.v1.ProxyRouteInfo 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 47, // 25: mcp.v1.ListProxyRoutesResponse.listeners:type_name -> mcp.v1.ProxyListenerInfo
3, // 26: mcp.v1.McpAgentService.Deploy:input_type -> mcp.v1.DeployRequest 59, // 26: mcp.v1.ListEdgeRoutesResponse.routes:type_name -> mcp.v1.EdgeRoute
12, // 27: mcp.v1.McpAgentService.UndeployService:input_type -> mcp.v1.UndeployServiceRequest 3, // 27: mcp.v1.McpAgentService.Deploy:input_type -> mcp.v1.DeployRequest
6, // 28: mcp.v1.McpAgentService.StopService:input_type -> mcp.v1.StopServiceRequest 12, // 28: mcp.v1.McpAgentService.UndeployService:input_type -> mcp.v1.UndeployServiceRequest
8, // 29: mcp.v1.McpAgentService.StartService:input_type -> mcp.v1.StartServiceRequest 6, // 29: mcp.v1.McpAgentService.StopService:input_type -> mcp.v1.StopServiceRequest
10, // 30: mcp.v1.McpAgentService.RestartService:input_type -> mcp.v1.RestartServiceRequest 8, // 30: mcp.v1.McpAgentService.StartService:input_type -> mcp.v1.StartServiceRequest
14, // 31: mcp.v1.McpAgentService.SyncDesiredState:input_type -> mcp.v1.SyncDesiredStateRequest 10, // 31: mcp.v1.McpAgentService.RestartService:input_type -> mcp.v1.RestartServiceRequest
17, // 32: mcp.v1.McpAgentService.ListServices:input_type -> mcp.v1.ListServicesRequest 14, // 32: mcp.v1.McpAgentService.SyncDesiredState:input_type -> mcp.v1.SyncDesiredStateRequest
21, // 33: mcp.v1.McpAgentService.GetServiceStatus:input_type -> mcp.v1.GetServiceStatusRequest 17, // 33: mcp.v1.McpAgentService.ListServices:input_type -> mcp.v1.ListServicesRequest
25, // 34: mcp.v1.McpAgentService.LiveCheck:input_type -> mcp.v1.LiveCheckRequest 21, // 34: mcp.v1.McpAgentService.GetServiceStatus:input_type -> mcp.v1.GetServiceStatusRequest
27, // 35: mcp.v1.McpAgentService.AdoptContainers:input_type -> mcp.v1.AdoptContainersRequest 25, // 35: mcp.v1.McpAgentService.LiveCheck:input_type -> mcp.v1.LiveCheckRequest
36, // 36: mcp.v1.McpAgentService.PurgeComponent:input_type -> mcp.v1.PurgeRequest 27, // 36: mcp.v1.McpAgentService.AdoptContainers:input_type -> mcp.v1.AdoptContainersRequest
30, // 37: mcp.v1.McpAgentService.PushFile:input_type -> mcp.v1.PushFileRequest 36, // 37: mcp.v1.McpAgentService.PurgeComponent:input_type -> mcp.v1.PurgeRequest
32, // 38: mcp.v1.McpAgentService.PullFile:input_type -> mcp.v1.PullFileRequest 30, // 38: mcp.v1.McpAgentService.PushFile:input_type -> mcp.v1.PushFileRequest
34, // 39: mcp.v1.McpAgentService.NodeStatus:input_type -> mcp.v1.NodeStatusRequest 32, // 39: mcp.v1.McpAgentService.PullFile:input_type -> mcp.v1.PullFileRequest
41, // 40: mcp.v1.McpAgentService.ListDNSRecords:input_type -> mcp.v1.ListDNSRecordsRequest 34, // 40: mcp.v1.McpAgentService.NodeStatus:input_type -> mcp.v1.NodeStatusRequest
45, // 41: mcp.v1.McpAgentService.ListProxyRoutes:input_type -> mcp.v1.ListProxyRoutesRequest 41, // 41: mcp.v1.McpAgentService.ListDNSRecords:input_type -> mcp.v1.ListDNSRecordsRequest
49, // 42: mcp.v1.McpAgentService.AddProxyRoute:input_type -> mcp.v1.AddProxyRouteRequest 45, // 42: mcp.v1.McpAgentService.ListProxyRoutes:input_type -> mcp.v1.ListProxyRoutesRequest
51, // 43: mcp.v1.McpAgentService.RemoveProxyRoute:input_type -> mcp.v1.RemoveProxyRouteRequest 49, // 43: mcp.v1.McpAgentService.AddProxyRoute:input_type -> mcp.v1.AddProxyRouteRequest
39, // 44: mcp.v1.McpAgentService.Logs:input_type -> mcp.v1.LogsRequest 51, // 44: mcp.v1.McpAgentService.RemoveProxyRoute:input_type -> mcp.v1.RemoveProxyRouteRequest
4, // 45: mcp.v1.McpAgentService.Deploy:output_type -> mcp.v1.DeployResponse 53, // 45: mcp.v1.McpAgentService.SetupEdgeRoute:input_type -> mcp.v1.SetupEdgeRouteRequest
13, // 46: mcp.v1.McpAgentService.UndeployService:output_type -> mcp.v1.UndeployServiceResponse 55, // 46: mcp.v1.McpAgentService.RemoveEdgeRoute:input_type -> mcp.v1.RemoveEdgeRouteRequest
7, // 47: mcp.v1.McpAgentService.StopService:output_type -> mcp.v1.StopServiceResponse 57, // 47: mcp.v1.McpAgentService.ListEdgeRoutes:input_type -> mcp.v1.ListEdgeRoutesRequest
9, // 48: mcp.v1.McpAgentService.StartService:output_type -> mcp.v1.StartServiceResponse 60, // 48: mcp.v1.McpAgentService.HealthCheck:input_type -> mcp.v1.HealthCheckRequest
11, // 49: mcp.v1.McpAgentService.RestartService:output_type -> mcp.v1.RestartServiceResponse 39, // 49: mcp.v1.McpAgentService.Logs:input_type -> mcp.v1.LogsRequest
15, // 50: mcp.v1.McpAgentService.SyncDesiredState:output_type -> mcp.v1.SyncDesiredStateResponse 4, // 50: mcp.v1.McpAgentService.Deploy:output_type -> mcp.v1.DeployResponse
20, // 51: mcp.v1.McpAgentService.ListServices:output_type -> mcp.v1.ListServicesResponse 13, // 51: mcp.v1.McpAgentService.UndeployService:output_type -> mcp.v1.UndeployServiceResponse
24, // 52: mcp.v1.McpAgentService.GetServiceStatus:output_type -> mcp.v1.GetServiceStatusResponse 7, // 52: mcp.v1.McpAgentService.StopService:output_type -> mcp.v1.StopServiceResponse
26, // 53: mcp.v1.McpAgentService.LiveCheck:output_type -> mcp.v1.LiveCheckResponse 9, // 53: mcp.v1.McpAgentService.StartService:output_type -> mcp.v1.StartServiceResponse
29, // 54: mcp.v1.McpAgentService.AdoptContainers:output_type -> mcp.v1.AdoptContainersResponse 11, // 54: mcp.v1.McpAgentService.RestartService:output_type -> mcp.v1.RestartServiceResponse
37, // 55: mcp.v1.McpAgentService.PurgeComponent:output_type -> mcp.v1.PurgeResponse 15, // 55: mcp.v1.McpAgentService.SyncDesiredState:output_type -> mcp.v1.SyncDesiredStateResponse
31, // 56: mcp.v1.McpAgentService.PushFile:output_type -> mcp.v1.PushFileResponse 20, // 56: mcp.v1.McpAgentService.ListServices:output_type -> mcp.v1.ListServicesResponse
33, // 57: mcp.v1.McpAgentService.PullFile:output_type -> mcp.v1.PullFileResponse 24, // 57: mcp.v1.McpAgentService.GetServiceStatus:output_type -> mcp.v1.GetServiceStatusResponse
35, // 58: mcp.v1.McpAgentService.NodeStatus:output_type -> mcp.v1.NodeStatusResponse 26, // 58: mcp.v1.McpAgentService.LiveCheck:output_type -> mcp.v1.LiveCheckResponse
44, // 59: mcp.v1.McpAgentService.ListDNSRecords:output_type -> mcp.v1.ListDNSRecordsResponse 29, // 59: mcp.v1.McpAgentService.AdoptContainers:output_type -> mcp.v1.AdoptContainersResponse
48, // 60: mcp.v1.McpAgentService.ListProxyRoutes:output_type -> mcp.v1.ListProxyRoutesResponse 37, // 60: mcp.v1.McpAgentService.PurgeComponent:output_type -> mcp.v1.PurgeResponse
50, // 61: mcp.v1.McpAgentService.AddProxyRoute:output_type -> mcp.v1.AddProxyRouteResponse 31, // 61: mcp.v1.McpAgentService.PushFile:output_type -> mcp.v1.PushFileResponse
52, // 62: mcp.v1.McpAgentService.RemoveProxyRoute:output_type -> mcp.v1.RemoveProxyRouteResponse 33, // 62: mcp.v1.McpAgentService.PullFile:output_type -> mcp.v1.PullFileResponse
40, // 63: mcp.v1.McpAgentService.Logs:output_type -> mcp.v1.LogsResponse 35, // 63: mcp.v1.McpAgentService.NodeStatus:output_type -> mcp.v1.NodeStatusResponse
45, // [45:64] is the sub-list for method output_type 44, // 64: mcp.v1.McpAgentService.ListDNSRecords:output_type -> mcp.v1.ListDNSRecordsResponse
26, // [26:45] is the sub-list for method input_type 48, // 65: mcp.v1.McpAgentService.ListProxyRoutes:output_type -> mcp.v1.ListProxyRoutesResponse
26, // [26:26] is the sub-list for extension type_name 50, // 66: mcp.v1.McpAgentService.AddProxyRoute:output_type -> mcp.v1.AddProxyRouteResponse
26, // [26:26] is the sub-list for extension extendee 52, // 67: mcp.v1.McpAgentService.RemoveProxyRoute:output_type -> mcp.v1.RemoveProxyRouteResponse
0, // [0:26] is the sub-list for field type_name 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() } func init() { file_proto_mcp_v1_mcp_proto_init() }
@@ -3403,7 +3924,7 @@ func file_proto_mcp_v1_mcp_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 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)), RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_mcp_v1_mcp_proto_rawDesc), len(file_proto_mcp_v1_mcp_proto_rawDesc)),
NumEnums: 0, NumEnums: 0,
NumMessages: 53, NumMessages: 62,
NumExtensions: 0, NumExtensions: 0,
NumServices: 1, NumServices: 1,
}, },

View File

@@ -37,6 +37,10 @@ const (
McpAgentService_ListProxyRoutes_FullMethodName = "/mcp.v1.McpAgentService/ListProxyRoutes" McpAgentService_ListProxyRoutes_FullMethodName = "/mcp.v1.McpAgentService/ListProxyRoutes"
McpAgentService_AddProxyRoute_FullMethodName = "/mcp.v1.McpAgentService/AddProxyRoute" McpAgentService_AddProxyRoute_FullMethodName = "/mcp.v1.McpAgentService/AddProxyRoute"
McpAgentService_RemoveProxyRoute_FullMethodName = "/mcp.v1.McpAgentService/RemoveProxyRoute" 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" 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) ListProxyRoutes(ctx context.Context, in *ListProxyRoutesRequest, opts ...grpc.CallOption) (*ListProxyRoutesResponse, error)
AddProxyRoute(ctx context.Context, in *AddProxyRouteRequest, opts ...grpc.CallOption) (*AddProxyRouteResponse, error) AddProxyRoute(ctx context.Context, in *AddProxyRouteRequest, opts ...grpc.CallOption) (*AddProxyRouteResponse, error)
RemoveProxyRoute(ctx context.Context, in *RemoveProxyRouteRequest, opts ...grpc.CallOption) (*RemoveProxyRouteResponse, 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
Logs(ctx context.Context, in *LogsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[LogsResponse], error) 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 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) { func (c *mcpAgentServiceClient) Logs(ctx context.Context, in *LogsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[LogsResponse], error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
stream, err := c.cc.NewStream(ctx, &McpAgentService_ServiceDesc.Streams[0], McpAgentService_Logs_FullMethodName, cOpts...) 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) ListProxyRoutes(context.Context, *ListProxyRoutesRequest) (*ListProxyRoutesResponse, error)
AddProxyRoute(context.Context, *AddProxyRouteRequest) (*AddProxyRouteResponse, error) AddProxyRoute(context.Context, *AddProxyRouteRequest) (*AddProxyRouteResponse, error)
RemoveProxyRoute(context.Context, *RemoveProxyRouteRequest) (*RemoveProxyRouteResponse, 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
Logs(*LogsRequest, grpc.ServerStreamingServer[LogsResponse]) error Logs(*LogsRequest, grpc.ServerStreamingServer[LogsResponse]) error
mustEmbedUnimplementedMcpAgentServiceServer() mustEmbedUnimplementedMcpAgentServiceServer()
@@ -379,6 +435,18 @@ func (UnimplementedMcpAgentServiceServer) AddProxyRoute(context.Context, *AddPro
func (UnimplementedMcpAgentServiceServer) RemoveProxyRoute(context.Context, *RemoveProxyRouteRequest) (*RemoveProxyRouteResponse, error) { func (UnimplementedMcpAgentServiceServer) RemoveProxyRoute(context.Context, *RemoveProxyRouteRequest) (*RemoveProxyRouteResponse, error) {
return nil, status.Error(codes.Unimplemented, "method RemoveProxyRoute not implemented") 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 { func (UnimplementedMcpAgentServiceServer) Logs(*LogsRequest, grpc.ServerStreamingServer[LogsResponse]) error {
return status.Error(codes.Unimplemented, "method Logs not implemented") 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) 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 { func _McpAgentService_Logs_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(LogsRequest) m := new(LogsRequest)
if err := stream.RecvMsg(m); err != nil { if err := stream.RecvMsg(m); err != nil {
@@ -817,6 +957,22 @@ var McpAgentService_ServiceDesc = grpc.ServiceDesc{
MethodName: "RemoveProxyRoute", MethodName: "RemoveProxyRoute",
Handler: _McpAgentService_RemoveProxyRoute_Handler, 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{ Streams: []grpc.StreamDesc{
{ {

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" "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) { 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() == "" { if req.GetName() == "" {
return nil, status.Error(codes.InvalidArgument, "service name is required") 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) 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 var results []*mcpv1.ComponentResult
for _, c := range components { for _, c := range components {
containerName := ContainerNameFor(req.GetName(), c.Name) 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 return &mcpv1.StopServiceResponse{Results: results}, nil
} }
// StartService starts all components of a service. If a container already // StartService starts all components of a service, or a single component if specified.
// exists but is stopped, it is removed first so a fresh one can be created. // 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) { 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() == "" { if req.GetName() == "" {
return nil, status.Error(codes.InvalidArgument, "service name is required") 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) 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 var results []*mcpv1.ComponentResult
for _, c := range components { for _, c := range components {
r := startComponent(ctx, a, req.GetName(), &c) 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 return &mcpv1.StartServiceResponse{Results: results}, nil
} }
// RestartService restarts all components of a service by stopping, removing, // RestartService restarts all components of a service, or a single component if specified,
// and re-creating each container. The desired_state is not changed. // 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) { 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() == "" { if req.GetName() == "" {
return nil, status.Error(codes.InvalidArgument, "service name is required") 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) 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 var results []*mcpv1.ComponentResult
for _, c := range components { for _, c := range components {
r := restartComponent(ctx, a, req.GetName(), &c) 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. // componentExists checks whether a component already exists in the registry.
func componentExists(db *sql.DB, service, name string) bool { func componentExists(db *sql.DB, service, name string) bool {
_, err := registry.GetComponent(db, service, name) _, err := registry.GetComponent(db, service, name)

View File

@@ -48,6 +48,16 @@ func (p *ProxyRouter) Close() error {
return p.client.Close() 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. // GetStatus returns the mc-proxy server status.
func (p *ProxyRouter) GetStatus(ctx context.Context) (*mcproxy.Status, error) { func (p *ProxyRouter) GetStatus(ctx context.Context) (*mcproxy.Status, error) {
if p == nil { if p == nil {

View File

@@ -142,4 +142,18 @@ var migrations = []string{
FOREIGN KEY (service, component) REFERENCES components(service, name) ON DELETE CASCADE 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 AddProxyRoute(AddProxyRouteRequest) returns (AddProxyRouteResponse);
rpc RemoveProxyRoute(RemoveProxyRouteRequest) returns (RemoveProxyRouteResponse); 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 // Logs
rpc Logs(LogsRequest) returns (stream LogsResponse); rpc Logs(LogsRequest) returns (stream LogsResponse);
} }
@@ -72,6 +80,7 @@ message ServiceSpec {
string name = 1; string name = 1;
bool active = 2; bool active = 2;
repeated ComponentSpec components = 3; repeated ComponentSpec components = 3;
string comment = 4;
} }
message DeployRequest { message DeployRequest {
@@ -92,6 +101,7 @@ message ComponentResult {
message StopServiceRequest { message StopServiceRequest {
string name = 1; string name = 1;
string component = 2;
} }
message StopServiceResponse { message StopServiceResponse {
@@ -100,6 +110,7 @@ message StopServiceResponse {
message StartServiceRequest { message StartServiceRequest {
string name = 1; string name = 1;
string component = 2;
} }
message StartServiceResponse { message StartServiceResponse {
@@ -108,6 +119,7 @@ message StartServiceResponse {
message RestartServiceRequest { message RestartServiceRequest {
string name = 1; string name = 1;
string component = 2;
} }
message RestartServiceResponse { message RestartServiceResponse {
@@ -148,6 +160,7 @@ message ServiceInfo {
string name = 1; string name = 1;
bool active = 2; bool active = 2;
repeated ComponentInfo components = 3; repeated ComponentInfo components = 3;
string comment = 4;
} }
message ComponentInfo { message ComponentInfo {
@@ -374,3 +387,43 @@ message RemoveProxyRouteRequest {
} }
message RemoveProxyRouteResponse {} 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;
}