Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 93e26d3789 | |||
| 3d2edb7c26 | |||
| bf02935716 | |||
| c4f0d7be8e | |||
| 4d900eafd1 | |||
| 38f9070c24 | |||
| 67d0ab1d9d | |||
| 7383b370f0 | |||
| 4c847e6de9 | |||
| 14b978861f | |||
| 18365cc0a8 |
151
ARCHITECTURE.md
151
ARCHITECTURE.md
@@ -121,9 +121,26 @@ option for future security hardening.
|
|||||||
## Authentication and Authorization
|
## Authentication and Authorization
|
||||||
|
|
||||||
MCP follows the platform authentication model: all auth is delegated to
|
MCP follows the platform authentication model: all auth is delegated to
|
||||||
MCIAS.
|
MCIAS. The auth model separates three concerns: operator intent (CLI to
|
||||||
|
agent), infrastructure automation (agent to platform services), and
|
||||||
|
access control (who can do what).
|
||||||
|
|
||||||
### Agent Authentication
|
### Identity Model
|
||||||
|
|
||||||
|
| Identity | Type | Purpose |
|
||||||
|
|----------|------|---------|
|
||||||
|
| Human operator (e.g., `kyle`) | human | CLI operations: deploy, stop, start, build |
|
||||||
|
| `mcp-agent` | system | Agent-to-service automation: certs, DNS, routes, image pull |
|
||||||
|
| Per-service accounts (e.g., `mcq`) | system | Scoped self-management (own DNS records only) |
|
||||||
|
| `admin` role | role | MCIAS account management, policy changes, zone creation |
|
||||||
|
| `guest` role | role | Explicitly rejected by the agent |
|
||||||
|
|
||||||
|
The `admin` role is reserved for MCIAS-level administrative operations
|
||||||
|
(account creation, policy management, zone mutations). Routine MCP
|
||||||
|
operations (deploy, stop, start, build) do not require admin — any
|
||||||
|
authenticated non-guest user or system account is accepted.
|
||||||
|
|
||||||
|
### Agent Authentication (CLI → Agent)
|
||||||
|
|
||||||
The agent is a gRPC server with a unary interceptor that enforces
|
The agent is a gRPC server with a unary interceptor that enforces
|
||||||
authentication on every RPC:
|
authentication on every RPC:
|
||||||
@@ -132,10 +149,34 @@ authentication on every RPC:
|
|||||||
(`authorization: Bearer <token>`).
|
(`authorization: Bearer <token>`).
|
||||||
2. Agent extracts the token and validates it against MCIAS (cached 30s by
|
2. Agent extracts the token and validates it against MCIAS (cached 30s by
|
||||||
SHA-256 of the token, per platform convention).
|
SHA-256 of the token, per platform convention).
|
||||||
3. Agent checks that the caller has the `admin` role. All MCP operations
|
3. Agent rejects guests (`guest` role → `PERMISSION_DENIED`). All other
|
||||||
require admin -- there is no unprivileged MCP access.
|
authenticated users and system accounts are accepted.
|
||||||
4. If validation fails, the RPC returns `UNAUTHENTICATED` (invalid/expired
|
4. If validation fails, the RPC returns `UNAUTHENTICATED` (invalid/expired
|
||||||
token) or `PERMISSION_DENIED` (valid token, not admin).
|
token) or `PERMISSION_DENIED` (guest).
|
||||||
|
|
||||||
|
### Agent Service Authentication (Agent → Platform Services)
|
||||||
|
|
||||||
|
The agent authenticates to platform services using a long-lived system
|
||||||
|
account token (`mcp-agent`). Each service has its own token file:
|
||||||
|
|
||||||
|
| Service | Token Path | Operations |
|
||||||
|
|---------|------------|------------|
|
||||||
|
| Metacrypt | `/srv/mcp/metacrypt-token` | TLS cert provisioning (PKI issue) |
|
||||||
|
| MCNS | `/srv/mcp/mcns-token` | DNS record create/delete (any name) |
|
||||||
|
| mc-proxy | Unix socket (no auth) | Route registration/removal |
|
||||||
|
| MCR | podman auth store | Image pull (JWT-as-password) |
|
||||||
|
|
||||||
|
These tokens are issued by MCIAS for the `mcp-agent` system account.
|
||||||
|
They carry no roles — authorization is handled by each service's policy
|
||||||
|
engine:
|
||||||
|
|
||||||
|
- **Metacrypt:** Policy rule grants `mcp-agent` write access to
|
||||||
|
`engine/pki/issue`.
|
||||||
|
- **MCNS:** Code-level authorization: system account `mcp-agent` can
|
||||||
|
manage any record; other system accounts can only manage records
|
||||||
|
matching their username.
|
||||||
|
- **MCR:** Default policy allows all authenticated users to push/pull.
|
||||||
|
MCR accepts MCIAS JWTs as passwords at the `/v2/token` endpoint.
|
||||||
|
|
||||||
### CLI Authentication
|
### CLI Authentication
|
||||||
|
|
||||||
@@ -148,6 +189,15 @@ obtained by:
|
|||||||
|
|
||||||
The stored token is used for all subsequent agent RPCs until it expires.
|
The stored token is used for all subsequent agent RPCs until it expires.
|
||||||
|
|
||||||
|
### MCR Registry Authentication
|
||||||
|
|
||||||
|
`mcp build` auto-authenticates to MCR before pushing images. It reads
|
||||||
|
the CLI's stored MCIAS token and uses it as the password for `podman
|
||||||
|
login`. MCR's token endpoint accepts MCIAS JWTs as passwords (the
|
||||||
|
personal-access-token pattern), so both human and system account tokens
|
||||||
|
work. This eliminates the need for a separate interactive `podman login`
|
||||||
|
step.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Services and Components
|
## Services and Components
|
||||||
@@ -224,6 +274,9 @@ mcp pull <service> <path> [local-file] Copy a file from /srv/<service>/<path> to
|
|||||||
mcp node list List registered nodes
|
mcp node list List registered nodes
|
||||||
mcp node add <name> <address> Register a node
|
mcp node add <name> <address> Register a node
|
||||||
mcp node remove <name> Deregister a node
|
mcp node remove <name> Deregister a node
|
||||||
|
|
||||||
|
mcp agent upgrade [node] Build, push, and restart agent on all (or one) node(s)
|
||||||
|
mcp agent status Show agent version on each node
|
||||||
```
|
```
|
||||||
|
|
||||||
### Service Definition Files
|
### Service Definition Files
|
||||||
@@ -1144,20 +1197,84 @@ The agent's data directory follows the platform convention:
|
|||||||
|
|
||||||
### Agent Deployment (on nodes)
|
### Agent Deployment (on nodes)
|
||||||
|
|
||||||
The agent is deployed like any other Metacircular service:
|
#### Provisioning (one-time per node)
|
||||||
|
|
||||||
1. Provision the `mcp` system user via NixOS config (with podman access
|
Each node needs a one-time setup before the agent can run. The steps are
|
||||||
and subuid/subgid ranges for rootless containers).
|
the same regardless of OS, but the mechanism differs:
|
||||||
|
|
||||||
|
1. Create `mcp` system user with podman access and subuid/subgid ranges.
|
||||||
2. Set `/srv/` ownership to the `mcp` user (the agent creates and manages
|
2. Set `/srv/` ownership to the `mcp` user (the agent creates and manages
|
||||||
`/srv/<service>/` directories for all services).
|
`/srv/<service>/` directories for all services).
|
||||||
3. Create `/srv/mcp/` directory and config file.
|
3. Create `/srv/mcp/` directory and config file.
|
||||||
4. Provision TLS certificate from Metacrypt.
|
4. Provision TLS certificate from Metacrypt.
|
||||||
5. Create an MCIAS system account for the agent (`mcp-agent`).
|
5. Create an MCIAS system account for the agent (`mcp-agent`).
|
||||||
6. Install the `mcp-agent` binary.
|
6. Install the initial `mcp-agent` binary to `/srv/mcp/mcp-agent`.
|
||||||
7. Start via systemd unit.
|
7. Install and start the systemd unit.
|
||||||
|
|
||||||
The agent runs as a systemd service. Container-first deployment is a v2
|
On **NixOS** (rift), provisioning is declarative via the NixOS config.
|
||||||
concern -- MCP needs to be running before it can manage its own agent.
|
The NixOS config owns the infrastructure (user, systemd unit, podman,
|
||||||
|
directories, permissions) but **not** the binary. `ExecStart` points to
|
||||||
|
`/srv/mcp/mcp-agent`, a mutable path that MCP manages. NixOS may
|
||||||
|
bootstrap the initial binary there, but subsequent updates come from MCP.
|
||||||
|
|
||||||
|
On **Debian** (hyperborea, svc), provisioning is done via a setup script
|
||||||
|
or ansible playbook that creates the same layout.
|
||||||
|
|
||||||
|
#### Binary Location
|
||||||
|
|
||||||
|
The agent binary lives at `/srv/mcp/mcp-agent` on **all** nodes,
|
||||||
|
regardless of OS. This unifies the update mechanism across the fleet.
|
||||||
|
|
||||||
|
#### Agent Upgrades
|
||||||
|
|
||||||
|
After initial provisioning, the agent binary is updated via
|
||||||
|
`mcp agent upgrade`. The CLI:
|
||||||
|
|
||||||
|
1. Cross-compiles the agent for each target architecture
|
||||||
|
(`GOARCH=amd64` for rift/svc, `GOARCH=arm64` for hyperborea).
|
||||||
|
2. SSHs to each node, pushes the binary to `/srv/mcp/mcp-agent.new`.
|
||||||
|
3. Atomically swaps the binary (`mv mcp-agent.new mcp-agent`).
|
||||||
|
4. Restarts the systemd service (`systemctl restart mcp-agent`).
|
||||||
|
|
||||||
|
SSH is used instead of gRPC because:
|
||||||
|
- It works even when the agent is broken or has an incompatible version.
|
||||||
|
- The binary is ~17MB, which exceeds gRPC default message limits.
|
||||||
|
- No self-restart coordination needed.
|
||||||
|
|
||||||
|
The CLI uses `golang.org/x/crypto/ssh` for native SSH, keeping the
|
||||||
|
entire workflow in a single binary with no external tool dependencies.
|
||||||
|
|
||||||
|
#### Node Configuration
|
||||||
|
|
||||||
|
Node config includes SSH and architecture info for agent management:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[[nodes]]
|
||||||
|
name = "rift"
|
||||||
|
address = "100.95.252.120:9444"
|
||||||
|
ssh = "rift" # SSH host (from ~/.ssh/config or hostname)
|
||||||
|
arch = "amd64" # GOARCH for cross-compilation
|
||||||
|
|
||||||
|
[[nodes]]
|
||||||
|
name = "hyperborea"
|
||||||
|
address = "100.x.x.x:9444"
|
||||||
|
ssh = "hyperborea"
|
||||||
|
arch = "arm64"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Coordinated Upgrades
|
||||||
|
|
||||||
|
New MCP releases often add new RPCs. A CLI at v0.6.0 calling an agent
|
||||||
|
at v0.5.0 fails with `Unimplemented`. Therefore agent upgrades must be
|
||||||
|
coordinated: `mcp agent upgrade` (with no node argument) upgrades all
|
||||||
|
nodes before the CLI is used for other operations.
|
||||||
|
|
||||||
|
If a node fails to upgrade, it is reported but the others still proceed.
|
||||||
|
The operator can retry or investigate via SSH.
|
||||||
|
|
||||||
|
#### Systemd Unit
|
||||||
|
|
||||||
|
The systemd unit is the same on all nodes:
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
[Unit]
|
[Unit]
|
||||||
@@ -1167,7 +1284,7 @@ Wants=network-online.target
|
|||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=simple
|
Type=simple
|
||||||
ExecStart=/usr/local/bin/mcp-agent server --config /srv/mcp/mcp-agent.toml
|
ExecStart=/srv/mcp/mcp-agent server --config /srv/mcp/mcp-agent.toml
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
RestartSec=5
|
RestartSec=5
|
||||||
|
|
||||||
@@ -1175,17 +1292,14 @@ User=mcp
|
|||||||
Group=mcp
|
Group=mcp
|
||||||
|
|
||||||
NoNewPrivileges=true
|
NoNewPrivileges=true
|
||||||
ProtectSystem=strict
|
ProtectSystem=full
|
||||||
ProtectHome=true
|
ProtectHome=false
|
||||||
PrivateTmp=true
|
PrivateTmp=true
|
||||||
PrivateDevices=true
|
PrivateDevices=true
|
||||||
ProtectKernelTunables=true
|
ProtectKernelTunables=true
|
||||||
ProtectKernelModules=true
|
ProtectKernelModules=true
|
||||||
ProtectControlGroups=true
|
|
||||||
RestrictSUIDSGID=true
|
RestrictSUIDSGID=true
|
||||||
RestrictNamespaces=true
|
|
||||||
LockPersonality=true
|
LockPersonality=true
|
||||||
MemoryDenyWriteExecute=true
|
|
||||||
RestrictRealtime=true
|
RestrictRealtime=true
|
||||||
ReadWritePaths=/srv
|
ReadWritePaths=/srv
|
||||||
|
|
||||||
@@ -1195,6 +1309,7 @@ WantedBy=multi-user.target
|
|||||||
|
|
||||||
Note: `ReadWritePaths=/srv` (not `/srv/mcp`) because the agent writes
|
Note: `ReadWritePaths=/srv` (not `/srv/mcp`) because the agent writes
|
||||||
files to any service's `/srv/<service>/` directory on behalf of the CLI.
|
files to any service's `/srv/<service>/` directory on behalf of the CLI.
|
||||||
|
`ProtectHome=false` because the `mcp` user's home is `/srv/mcp`.
|
||||||
|
|
||||||
### CLI Installation (on operator workstation)
|
### CLI Installation (on operator workstation)
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ func main() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("load config: %w", err)
|
return fmt.Errorf("load config: %w", err)
|
||||||
}
|
}
|
||||||
return agent.Run(cfg)
|
return agent.Run(cfg, version)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ func dialAgent(address string, cfg *config.CLIConfig) (mcpv1.McpAgentServiceClie
|
|||||||
address,
|
address,
|
||||||
grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig)),
|
grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig)),
|
||||||
grpc.WithUnaryInterceptor(tokenInterceptor(token)),
|
grpc.WithUnaryInterceptor(tokenInterceptor(token)),
|
||||||
|
grpc.WithStreamInterceptor(streamTokenInterceptor(token)),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("dial %q: %w", address, err)
|
return nil, nil, fmt.Errorf("dial %q: %w", address, err)
|
||||||
@@ -60,6 +61,15 @@ func tokenInterceptor(token string) grpc.UnaryClientInterceptor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// streamTokenInterceptor returns a gRPC client stream interceptor that
|
||||||
|
// attaches the bearer token to outgoing stream metadata.
|
||||||
|
func streamTokenInterceptor(token string) grpc.StreamClientInterceptor {
|
||||||
|
return func(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) {
|
||||||
|
ctx = metadata.AppendToOutgoingContext(ctx, "authorization", "Bearer "+token)
|
||||||
|
return streamer(ctx, desc, cc, method, opts...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// loadBearerToken reads the token from file or env var.
|
// loadBearerToken reads the token from file or env var.
|
||||||
func loadBearerToken(cfg *config.CLIConfig) (string, error) {
|
func loadBearerToken(cfg *config.CLIConfig) (string, error) {
|
||||||
if token := os.Getenv("MCP_TOKEN"); token != "" {
|
if token := os.Getenv("MCP_TOKEN"); token != "" {
|
||||||
|
|||||||
87
cmd/mcp/dns.go
Normal file
87
cmd/mcp/dns.go
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
mcpv1 "git.wntrmute.dev/mc/mcp/gen/mcp/v1"
|
||||||
|
"git.wntrmute.dev/mc/mcp/internal/config"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
func dnsCmd() *cobra.Command {
|
||||||
|
return &cobra.Command{
|
||||||
|
Use: "dns",
|
||||||
|
Short: "List all DNS zones and records from MCNS",
|
||||||
|
RunE: runDNS,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func runDNS(_ *cobra.Command, _ []string) error {
|
||||||
|
cfg, err := config.LoadCLIConfig(cfgPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("load config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DNS is centralized — query the first reachable agent.
|
||||||
|
resp, nodeName, err := queryDNS(cfg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(resp.GetZones()) == 0 {
|
||||||
|
fmt.Println("no DNS zones configured")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = nodeName
|
||||||
|
for i, zone := range resp.GetZones() {
|
||||||
|
if i > 0 {
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
fmt.Printf("ZONE: %s\n", zone.GetName())
|
||||||
|
|
||||||
|
if len(zone.GetRecords()) == 0 {
|
||||||
|
fmt.Println(" (no records)")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
w := newTable()
|
||||||
|
_, _ = fmt.Fprintln(w, " NAME\tTYPE\tVALUE\tTTL")
|
||||||
|
for _, r := range zone.GetRecords() {
|
||||||
|
_, _ = fmt.Fprintf(w, " %s\t%s\t%s\t%d\n",
|
||||||
|
r.GetName(), r.GetType(), r.GetValue(), r.GetTtl())
|
||||||
|
}
|
||||||
|
_ = w.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// queryDNS tries each configured agent and returns the first successful
|
||||||
|
// DNS listing. DNS is centralized so any agent with MCNS configured works.
|
||||||
|
func queryDNS(cfg *config.CLIConfig) (*mcpv1.ListDNSRecordsResponse, string, error) {
|
||||||
|
for _, node := range cfg.Nodes {
|
||||||
|
client, conn, err := dialAgent(node.Address, cfg)
|
||||||
|
if err != nil {
|
||||||
|
_, _ = fmt.Fprintf(os.Stderr, "warning: %s: %v\n", node.Name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||||
|
resp, err := client.ListDNSRecords(ctx, &mcpv1.ListDNSRecordsRequest{})
|
||||||
|
cancel()
|
||||||
|
_ = conn.Close()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
_, _ = fmt.Fprintf(os.Stderr, "warning: %s: list DNS: %v\n", node.Name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, node.Name, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, "", fmt.Errorf("no reachable agent with DNS configured")
|
||||||
|
}
|
||||||
12
cmd/mcp/edit.go
Normal file
12
cmd/mcp/edit.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "github.com/spf13/cobra"
|
||||||
|
|
||||||
|
func editCmd() *cobra.Command {
|
||||||
|
return &cobra.Command{
|
||||||
|
Use: "edit <service>",
|
||||||
|
Short: "Open service definition in $EDITOR",
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
RunE: runServiceEdit,
|
||||||
|
}
|
||||||
|
}
|
||||||
81
cmd/mcp/logs.go
Normal file
81
cmd/mcp/logs.go
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
mcpv1 "git.wntrmute.dev/mc/mcp/gen/mcp/v1"
|
||||||
|
"git.wntrmute.dev/mc/mcp/internal/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
func logsCmd() *cobra.Command {
|
||||||
|
var (
|
||||||
|
tail int
|
||||||
|
follow bool
|
||||||
|
timestamps bool
|
||||||
|
since string
|
||||||
|
)
|
||||||
|
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "logs <service>[/<component>]",
|
||||||
|
Short: "Show container logs",
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
cfg, err := config.LoadCLIConfig(cfgPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("load config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
serviceName, component := parseServiceArg(args[0])
|
||||||
|
|
||||||
|
def, err := loadServiceDef(cmd, cfg, serviceName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
address, err := findNodeAddress(cfg, def.Node)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
client, conn, err := dialAgent(address, cfg)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("dial agent: %w", err)
|
||||||
|
}
|
||||||
|
defer func() { _ = conn.Close() }()
|
||||||
|
|
||||||
|
stream, err := client.Logs(cmd.Context(), &mcpv1.LogsRequest{
|
||||||
|
Service: serviceName,
|
||||||
|
Component: component,
|
||||||
|
Tail: int32(tail),
|
||||||
|
Follow: follow,
|
||||||
|
Timestamps: timestamps,
|
||||||
|
Since: since,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("logs: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
resp, err := stream.Recv()
|
||||||
|
if err == io.EOF {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("recv: %w", err)
|
||||||
|
}
|
||||||
|
_, _ = os.Stdout.Write(resp.Data)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Flags().IntVarP(&tail, "tail", "n", 0, "number of lines from end (0 = all)")
|
||||||
|
cmd.Flags().BoolVarP(&follow, "follow", "f", false, "follow log output")
|
||||||
|
cmd.Flags().BoolVarP(×tamps, "timestamps", "t", false, "show timestamps")
|
||||||
|
cmd.Flags().StringVar(&since, "since", "", "show logs since (e.g., 2h, 2026-03-28T00:00:00Z)")
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
@@ -50,6 +50,9 @@ func main() {
|
|||||||
root.AddCommand(pullCmd())
|
root.AddCommand(pullCmd())
|
||||||
root.AddCommand(nodeCmd())
|
root.AddCommand(nodeCmd())
|
||||||
root.AddCommand(purgeCmd())
|
root.AddCommand(purgeCmd())
|
||||||
|
root.AddCommand(logsCmd())
|
||||||
|
root.AddCommand(editCmd())
|
||||||
|
root.AddCommand(dnsCmd())
|
||||||
|
|
||||||
if err := root.Execute(); err != nil {
|
if err := root.Execute(); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
|
"time"
|
||||||
|
|
||||||
toml "github.com/pelletier/go-toml/v2"
|
toml "github.com/pelletier/go-toml/v2"
|
||||||
|
|
||||||
|
mcpv1 "git.wntrmute.dev/mc/mcp/gen/mcp/v1"
|
||||||
"git.wntrmute.dev/mc/mcp/internal/config"
|
"git.wntrmute.dev/mc/mcp/internal/config"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
@@ -37,10 +40,62 @@ func nodeCmd() *cobra.Command {
|
|||||||
RunE: runNodeRemove,
|
RunE: runNodeRemove,
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd.AddCommand(list, add, remove)
|
routes := &cobra.Command{
|
||||||
|
Use: "routes",
|
||||||
|
Short: "List mc-proxy routes on all nodes",
|
||||||
|
RunE: runNodeRoutes,
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.AddCommand(list, add, remove, routes)
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func runNodeRoutes(_ *cobra.Command, _ []string) error {
|
||||||
|
first := true
|
||||||
|
return forEachNode(func(node config.NodeConfig, client mcpv1.McpAgentServiceClient) error {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
resp, err := client.ListProxyRoutes(ctx, &mcpv1.ListProxyRoutesRequest{})
|
||||||
|
if err != nil {
|
||||||
|
_, _ = fmt.Fprintf(os.Stderr, "warning: %s: list routes: %v\n", node.Name, err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !first {
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
first = false
|
||||||
|
|
||||||
|
fmt.Printf("NODE: %s\n", node.Name)
|
||||||
|
fmt.Printf("mc-proxy %s\n", resp.GetVersion())
|
||||||
|
if resp.GetStartedAt() != nil {
|
||||||
|
uptime := time.Since(resp.GetStartedAt().AsTime()).Truncate(time.Second)
|
||||||
|
fmt.Printf("uptime: %s\n", uptime)
|
||||||
|
}
|
||||||
|
fmt.Printf("connections: %d\n", resp.GetTotalConnections())
|
||||||
|
fmt.Println()
|
||||||
|
|
||||||
|
for _, ls := range resp.GetListeners() {
|
||||||
|
fmt.Printf(" %s routes=%d active=%d\n",
|
||||||
|
ls.GetAddr(), ls.GetRouteCount(), ls.GetActiveConnections())
|
||||||
|
for _, r := range ls.GetRoutes() {
|
||||||
|
mode := r.GetMode()
|
||||||
|
if mode == "" {
|
||||||
|
mode = "l4"
|
||||||
|
}
|
||||||
|
extra := ""
|
||||||
|
if r.GetBackendTls() {
|
||||||
|
extra = " (re-encrypt)"
|
||||||
|
}
|
||||||
|
fmt.Printf(" %s %s → %s%s\n", mode, r.GetHostname(), r.GetBackend(), extra)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func runNodeList(_ *cobra.Command, _ []string) error {
|
func runNodeList(_ *cobra.Command, _ []string) error {
|
||||||
cfg, err := config.LoadCLIConfig(cfgPath)
|
cfg, err := config.LoadCLIConfig(cfgPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -48,13 +103,35 @@ func runNodeList(_ *cobra.Command, _ []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
w := tabwriter.NewWriter(os.Stdout, 0, 4, 2, ' ', 0)
|
w := tabwriter.NewWriter(os.Stdout, 0, 4, 2, ' ', 0)
|
||||||
_, _ = fmt.Fprintln(w, "NAME\tADDRESS")
|
_, _ = fmt.Fprintln(w, "NAME\tADDRESS\tVERSION")
|
||||||
for _, n := range cfg.Nodes {
|
for _, n := range cfg.Nodes {
|
||||||
_, _ = fmt.Fprintf(w, "%s\t%s\n", n.Name, n.Address)
|
ver := queryAgentVersion(cfg, n.Address)
|
||||||
|
_, _ = fmt.Fprintf(w, "%s\t%s\t%s\n", n.Name, n.Address, ver)
|
||||||
}
|
}
|
||||||
return w.Flush()
|
return w.Flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// queryAgentVersion dials the agent and returns its version, or an error indicator.
|
||||||
|
func queryAgentVersion(cfg *config.CLIConfig, address string) string {
|
||||||
|
client, conn, err := dialAgent(address, cfg)
|
||||||
|
if err != nil {
|
||||||
|
return "error"
|
||||||
|
}
|
||||||
|
defer func() { _ = conn.Close() }()
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
resp, err := client.NodeStatus(ctx, &mcpv1.NodeStatusRequest{})
|
||||||
|
if err != nil {
|
||||||
|
return "error"
|
||||||
|
}
|
||||||
|
if resp.AgentVersion == "" {
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
return resp.AgentVersion
|
||||||
|
}
|
||||||
|
|
||||||
func runNodeAdd(_ *cobra.Command, args []string) error {
|
func runNodeAdd(_ *cobra.Command, args []string) error {
|
||||||
cfg, err := config.LoadCLIConfig(cfgPath)
|
cfg, err := config.LoadCLIConfig(cfgPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
let
|
let
|
||||||
system = "x86_64-linux";
|
system = "x86_64-linux";
|
||||||
pkgs = nixpkgs.legacyPackages.${system};
|
pkgs = nixpkgs.legacyPackages.${system};
|
||||||
version = "0.6.0";
|
version = pkgs.lib.removePrefix "v" (self.gitDescribe or self.shortRev or self.dirtyShortRev or "unknown");
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
packages.${system} = {
|
packages.${system} = {
|
||||||
|
|||||||
@@ -1926,6 +1926,7 @@ type NodeStatusResponse struct {
|
|||||||
MemoryFreeBytes uint64 `protobuf:"varint,9,opt,name=memory_free_bytes,json=memoryFreeBytes,proto3" json:"memory_free_bytes,omitempty"`
|
MemoryFreeBytes uint64 `protobuf:"varint,9,opt,name=memory_free_bytes,json=memoryFreeBytes,proto3" json:"memory_free_bytes,omitempty"`
|
||||||
CpuUsagePercent float64 `protobuf:"fixed64,10,opt,name=cpu_usage_percent,json=cpuUsagePercent,proto3" json:"cpu_usage_percent,omitempty"`
|
CpuUsagePercent float64 `protobuf:"fixed64,10,opt,name=cpu_usage_percent,json=cpuUsagePercent,proto3" json:"cpu_usage_percent,omitempty"`
|
||||||
UptimeSince *timestamppb.Timestamp `protobuf:"bytes,11,opt,name=uptime_since,json=uptimeSince,proto3" json:"uptime_since,omitempty"`
|
UptimeSince *timestamppb.Timestamp `protobuf:"bytes,11,opt,name=uptime_since,json=uptimeSince,proto3" json:"uptime_since,omitempty"`
|
||||||
|
AgentVersion string `protobuf:"bytes,12,opt,name=agent_version,json=agentVersion,proto3" json:"agent_version,omitempty"`
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
@@ -2037,6 +2038,13 @@ func (x *NodeStatusResponse) GetUptimeSince() *timestamppb.Timestamp {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *NodeStatusResponse) GetAgentVersion() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.AgentVersion
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
type PurgeRequest struct {
|
type PurgeRequest struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
// Service name (empty = all services).
|
// Service name (empty = all services).
|
||||||
@@ -2224,6 +2232,582 @@ func (x *PurgeResult) GetReason() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type LogsRequest struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
Service string `protobuf:"bytes,1,opt,name=service,proto3" json:"service,omitempty"`
|
||||||
|
Component string `protobuf:"bytes,2,opt,name=component,proto3" json:"component,omitempty"` // optional; defaults to first/only component
|
||||||
|
Tail int32 `protobuf:"varint,3,opt,name=tail,proto3" json:"tail,omitempty"` // number of lines from the end (0 = all)
|
||||||
|
Follow bool `protobuf:"varint,4,opt,name=follow,proto3" json:"follow,omitempty"` // stream new output
|
||||||
|
Timestamps bool `protobuf:"varint,5,opt,name=timestamps,proto3" json:"timestamps,omitempty"` // prepend timestamps
|
||||||
|
Since string `protobuf:"bytes,6,opt,name=since,proto3" json:"since,omitempty"` // show logs since (e.g., "2h", "2026-03-28T00:00:00Z")
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LogsRequest) Reset() {
|
||||||
|
*x = LogsRequest{}
|
||||||
|
mi := &file_proto_mcp_v1_mcp_proto_msgTypes[39]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LogsRequest) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*LogsRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *LogsRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_proto_mcp_v1_mcp_proto_msgTypes[39]
|
||||||
|
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 LogsRequest.ProtoReflect.Descriptor instead.
|
||||||
|
func (*LogsRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{39}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LogsRequest) GetService() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Service
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LogsRequest) GetComponent() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Component
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LogsRequest) GetTail() int32 {
|
||||||
|
if x != nil {
|
||||||
|
return x.Tail
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LogsRequest) GetFollow() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.Follow
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LogsRequest) GetTimestamps() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.Timestamps
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LogsRequest) GetSince() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Since
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type LogsResponse struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LogsResponse) Reset() {
|
||||||
|
*x = LogsResponse{}
|
||||||
|
mi := &file_proto_mcp_v1_mcp_proto_msgTypes[40]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LogsResponse) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*LogsResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *LogsResponse) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_proto_mcp_v1_mcp_proto_msgTypes[40]
|
||||||
|
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 LogsResponse.ProtoReflect.Descriptor instead.
|
||||||
|
func (*LogsResponse) Descriptor() ([]byte, []int) {
|
||||||
|
return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{40}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LogsResponse) GetData() []byte {
|
||||||
|
if x != nil {
|
||||||
|
return x.Data
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListDNSRecordsRequest struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ListDNSRecordsRequest) Reset() {
|
||||||
|
*x = ListDNSRecordsRequest{}
|
||||||
|
mi := &file_proto_mcp_v1_mcp_proto_msgTypes[41]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ListDNSRecordsRequest) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*ListDNSRecordsRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *ListDNSRecordsRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_proto_mcp_v1_mcp_proto_msgTypes[41]
|
||||||
|
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 ListDNSRecordsRequest.ProtoReflect.Descriptor instead.
|
||||||
|
func (*ListDNSRecordsRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{41}
|
||||||
|
}
|
||||||
|
|
||||||
|
type DNSZone struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
|
||||||
|
Records []*DNSRecord `protobuf:"bytes,2,rep,name=records,proto3" json:"records,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DNSZone) Reset() {
|
||||||
|
*x = DNSZone{}
|
||||||
|
mi := &file_proto_mcp_v1_mcp_proto_msgTypes[42]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DNSZone) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*DNSZone) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *DNSZone) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_proto_mcp_v1_mcp_proto_msgTypes[42]
|
||||||
|
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 DNSZone.ProtoReflect.Descriptor instead.
|
||||||
|
func (*DNSZone) Descriptor() ([]byte, []int) {
|
||||||
|
return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{42}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DNSZone) GetName() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Name
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DNSZone) GetRecords() []*DNSRecord {
|
||||||
|
if x != nil {
|
||||||
|
return x.Records
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type DNSRecord struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||||
|
Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
|
||||||
|
Type string `protobuf:"bytes,3,opt,name=type,proto3" json:"type,omitempty"`
|
||||||
|
Value string `protobuf:"bytes,4,opt,name=value,proto3" json:"value,omitempty"`
|
||||||
|
Ttl int32 `protobuf:"varint,5,opt,name=ttl,proto3" json:"ttl,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DNSRecord) Reset() {
|
||||||
|
*x = DNSRecord{}
|
||||||
|
mi := &file_proto_mcp_v1_mcp_proto_msgTypes[43]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DNSRecord) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*DNSRecord) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *DNSRecord) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_proto_mcp_v1_mcp_proto_msgTypes[43]
|
||||||
|
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 DNSRecord.ProtoReflect.Descriptor instead.
|
||||||
|
func (*DNSRecord) Descriptor() ([]byte, []int) {
|
||||||
|
return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{43}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DNSRecord) GetId() int64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.Id
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DNSRecord) GetName() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Name
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DNSRecord) GetType() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Type
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DNSRecord) GetValue() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Value
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DNSRecord) GetTtl() int32 {
|
||||||
|
if x != nil {
|
||||||
|
return x.Ttl
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListDNSRecordsResponse struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
Zones []*DNSZone `protobuf:"bytes,1,rep,name=zones,proto3" json:"zones,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ListDNSRecordsResponse) Reset() {
|
||||||
|
*x = ListDNSRecordsResponse{}
|
||||||
|
mi := &file_proto_mcp_v1_mcp_proto_msgTypes[44]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ListDNSRecordsResponse) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*ListDNSRecordsResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *ListDNSRecordsResponse) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_proto_mcp_v1_mcp_proto_msgTypes[44]
|
||||||
|
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 ListDNSRecordsResponse.ProtoReflect.Descriptor instead.
|
||||||
|
func (*ListDNSRecordsResponse) Descriptor() ([]byte, []int) {
|
||||||
|
return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{44}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ListDNSRecordsResponse) GetZones() []*DNSZone {
|
||||||
|
if x != nil {
|
||||||
|
return x.Zones
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListProxyRoutesRequest struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ListProxyRoutesRequest) Reset() {
|
||||||
|
*x = ListProxyRoutesRequest{}
|
||||||
|
mi := &file_proto_mcp_v1_mcp_proto_msgTypes[45]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ListProxyRoutesRequest) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*ListProxyRoutesRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *ListProxyRoutesRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_proto_mcp_v1_mcp_proto_msgTypes[45]
|
||||||
|
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 ListProxyRoutesRequest.ProtoReflect.Descriptor instead.
|
||||||
|
func (*ListProxyRoutesRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{45}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProxyRouteInfo struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
Hostname string `protobuf:"bytes,1,opt,name=hostname,proto3" json:"hostname,omitempty"`
|
||||||
|
Backend string `protobuf:"bytes,2,opt,name=backend,proto3" json:"backend,omitempty"`
|
||||||
|
Mode string `protobuf:"bytes,3,opt,name=mode,proto3" json:"mode,omitempty"`
|
||||||
|
BackendTls bool `protobuf:"varint,4,opt,name=backend_tls,json=backendTls,proto3" json:"backend_tls,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ProxyRouteInfo) Reset() {
|
||||||
|
*x = ProxyRouteInfo{}
|
||||||
|
mi := &file_proto_mcp_v1_mcp_proto_msgTypes[46]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ProxyRouteInfo) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*ProxyRouteInfo) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *ProxyRouteInfo) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_proto_mcp_v1_mcp_proto_msgTypes[46]
|
||||||
|
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 ProxyRouteInfo.ProtoReflect.Descriptor instead.
|
||||||
|
func (*ProxyRouteInfo) Descriptor() ([]byte, []int) {
|
||||||
|
return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{46}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ProxyRouteInfo) GetHostname() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Hostname
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ProxyRouteInfo) GetBackend() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Backend
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ProxyRouteInfo) GetMode() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Mode
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ProxyRouteInfo) GetBackendTls() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.BackendTls
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProxyListenerInfo struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
Addr string `protobuf:"bytes,1,opt,name=addr,proto3" json:"addr,omitempty"`
|
||||||
|
RouteCount int32 `protobuf:"varint,2,opt,name=route_count,json=routeCount,proto3" json:"route_count,omitempty"`
|
||||||
|
ActiveConnections int64 `protobuf:"varint,3,opt,name=active_connections,json=activeConnections,proto3" json:"active_connections,omitempty"`
|
||||||
|
Routes []*ProxyRouteInfo `protobuf:"bytes,4,rep,name=routes,proto3" json:"routes,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ProxyListenerInfo) Reset() {
|
||||||
|
*x = ProxyListenerInfo{}
|
||||||
|
mi := &file_proto_mcp_v1_mcp_proto_msgTypes[47]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ProxyListenerInfo) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*ProxyListenerInfo) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *ProxyListenerInfo) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_proto_mcp_v1_mcp_proto_msgTypes[47]
|
||||||
|
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 ProxyListenerInfo.ProtoReflect.Descriptor instead.
|
||||||
|
func (*ProxyListenerInfo) Descriptor() ([]byte, []int) {
|
||||||
|
return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{47}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ProxyListenerInfo) GetAddr() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Addr
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ProxyListenerInfo) GetRouteCount() int32 {
|
||||||
|
if x != nil {
|
||||||
|
return x.RouteCount
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ProxyListenerInfo) GetActiveConnections() int64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.ActiveConnections
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ProxyListenerInfo) GetRoutes() []*ProxyRouteInfo {
|
||||||
|
if x != nil {
|
||||||
|
return x.Routes
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListProxyRoutesResponse struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
Version string `protobuf:"bytes,1,opt,name=version,proto3" json:"version,omitempty"`
|
||||||
|
TotalConnections int64 `protobuf:"varint,2,opt,name=total_connections,json=totalConnections,proto3" json:"total_connections,omitempty"`
|
||||||
|
StartedAt *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=started_at,json=startedAt,proto3" json:"started_at,omitempty"`
|
||||||
|
Listeners []*ProxyListenerInfo `protobuf:"bytes,4,rep,name=listeners,proto3" json:"listeners,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ListProxyRoutesResponse) Reset() {
|
||||||
|
*x = ListProxyRoutesResponse{}
|
||||||
|
mi := &file_proto_mcp_v1_mcp_proto_msgTypes[48]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ListProxyRoutesResponse) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*ListProxyRoutesResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *ListProxyRoutesResponse) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_proto_mcp_v1_mcp_proto_msgTypes[48]
|
||||||
|
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 ListProxyRoutesResponse.ProtoReflect.Descriptor instead.
|
||||||
|
func (*ListProxyRoutesResponse) Descriptor() ([]byte, []int) {
|
||||||
|
return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{48}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ListProxyRoutesResponse) GetVersion() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Version
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ListProxyRoutesResponse) GetTotalConnections() int64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.TotalConnections
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ListProxyRoutesResponse) GetStartedAt() *timestamppb.Timestamp {
|
||||||
|
if x != nil {
|
||||||
|
return x.StartedAt
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ListProxyRoutesResponse) GetListeners() []*ProxyListenerInfo {
|
||||||
|
if x != nil {
|
||||||
|
return x.Listeners
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
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 = "" +
|
||||||
@@ -2346,7 +2930,7 @@ const file_proto_mcp_v1_mcp_proto_rawDesc = "" +
|
|||||||
"\acontent\x18\x01 \x01(\fR\acontent\x12\x12\n" +
|
"\acontent\x18\x01 \x01(\fR\acontent\x12\x12\n" +
|
||||||
"\x04mode\x18\x02 \x01(\rR\x04mode\x12\x14\n" +
|
"\x04mode\x18\x02 \x01(\rR\x04mode\x12\x14\n" +
|
||||||
"\x05error\x18\x03 \x01(\tR\x05error\"\x13\n" +
|
"\x05error\x18\x03 \x01(\tR\x05error\"\x13\n" +
|
||||||
"\x11NodeStatusRequest\"\xd9\x03\n" +
|
"\x11NodeStatusRequest\"\xfe\x03\n" +
|
||||||
"\x12NodeStatusResponse\x12\x1b\n" +
|
"\x12NodeStatusResponse\x12\x1b\n" +
|
||||||
"\tnode_name\x18\x01 \x01(\tR\bnodeName\x12\x18\n" +
|
"\tnode_name\x18\x01 \x01(\tR\bnodeName\x12\x18\n" +
|
||||||
"\aruntime\x18\x02 \x01(\tR\aruntime\x12'\n" +
|
"\aruntime\x18\x02 \x01(\tR\aruntime\x12'\n" +
|
||||||
@@ -2359,7 +2943,8 @@ const file_proto_mcp_v1_mcp_proto_rawDesc = "" +
|
|||||||
"\x11memory_free_bytes\x18\t \x01(\x04R\x0fmemoryFreeBytes\x12*\n" +
|
"\x11memory_free_bytes\x18\t \x01(\x04R\x0fmemoryFreeBytes\x12*\n" +
|
||||||
"\x11cpu_usage_percent\x18\n" +
|
"\x11cpu_usage_percent\x18\n" +
|
||||||
" \x01(\x01R\x0fcpuUsagePercent\x12=\n" +
|
" \x01(\x01R\x0fcpuUsagePercent\x12=\n" +
|
||||||
"\fuptime_since\x18\v \x01(\v2\x1a.google.protobuf.TimestampR\vuptimeSince\"\x8e\x01\n" +
|
"\fuptime_since\x18\v \x01(\v2\x1a.google.protobuf.TimestampR\vuptimeSince\x12#\n" +
|
||||||
|
"\ragent_version\x18\f \x01(\tR\fagentVersion\"\x8e\x01\n" +
|
||||||
"\fPurgeRequest\x12\x18\n" +
|
"\fPurgeRequest\x12\x18\n" +
|
||||||
"\aservice\x18\x01 \x01(\tR\aservice\x12\x1c\n" +
|
"\aservice\x18\x01 \x01(\tR\aservice\x12\x1c\n" +
|
||||||
"\tcomponent\x18\x02 \x01(\tR\tcomponent\x12\x17\n" +
|
"\tcomponent\x18\x02 \x01(\tR\tcomponent\x12\x17\n" +
|
||||||
@@ -2371,7 +2956,49 @@ const file_proto_mcp_v1_mcp_proto_rawDesc = "" +
|
|||||||
"\aservice\x18\x01 \x01(\tR\aservice\x12\x1c\n" +
|
"\aservice\x18\x01 \x01(\tR\aservice\x12\x1c\n" +
|
||||||
"\tcomponent\x18\x02 \x01(\tR\tcomponent\x12\x16\n" +
|
"\tcomponent\x18\x02 \x01(\tR\tcomponent\x12\x16\n" +
|
||||||
"\x06purged\x18\x03 \x01(\bR\x06purged\x12\x16\n" +
|
"\x06purged\x18\x03 \x01(\bR\x06purged\x12\x16\n" +
|
||||||
"\x06reason\x18\x04 \x01(\tR\x06reason2\x93\b\n" +
|
"\x06reason\x18\x04 \x01(\tR\x06reason\"\xa7\x01\n" +
|
||||||
|
"\vLogsRequest\x12\x18\n" +
|
||||||
|
"\aservice\x18\x01 \x01(\tR\aservice\x12\x1c\n" +
|
||||||
|
"\tcomponent\x18\x02 \x01(\tR\tcomponent\x12\x12\n" +
|
||||||
|
"\x04tail\x18\x03 \x01(\x05R\x04tail\x12\x16\n" +
|
||||||
|
"\x06follow\x18\x04 \x01(\bR\x06follow\x12\x1e\n" +
|
||||||
|
"\n" +
|
||||||
|
"timestamps\x18\x05 \x01(\bR\n" +
|
||||||
|
"timestamps\x12\x14\n" +
|
||||||
|
"\x05since\x18\x06 \x01(\tR\x05since\"\"\n" +
|
||||||
|
"\fLogsResponse\x12\x12\n" +
|
||||||
|
"\x04data\x18\x01 \x01(\fR\x04data\"\x17\n" +
|
||||||
|
"\x15ListDNSRecordsRequest\"J\n" +
|
||||||
|
"\aDNSZone\x12\x12\n" +
|
||||||
|
"\x04name\x18\x01 \x01(\tR\x04name\x12+\n" +
|
||||||
|
"\arecords\x18\x02 \x03(\v2\x11.mcp.v1.DNSRecordR\arecords\"k\n" +
|
||||||
|
"\tDNSRecord\x12\x0e\n" +
|
||||||
|
"\x02id\x18\x01 \x01(\x03R\x02id\x12\x12\n" +
|
||||||
|
"\x04name\x18\x02 \x01(\tR\x04name\x12\x12\n" +
|
||||||
|
"\x04type\x18\x03 \x01(\tR\x04type\x12\x14\n" +
|
||||||
|
"\x05value\x18\x04 \x01(\tR\x05value\x12\x10\n" +
|
||||||
|
"\x03ttl\x18\x05 \x01(\x05R\x03ttl\"?\n" +
|
||||||
|
"\x16ListDNSRecordsResponse\x12%\n" +
|
||||||
|
"\x05zones\x18\x01 \x03(\v2\x0f.mcp.v1.DNSZoneR\x05zones\"\x18\n" +
|
||||||
|
"\x16ListProxyRoutesRequest\"{\n" +
|
||||||
|
"\x0eProxyRouteInfo\x12\x1a\n" +
|
||||||
|
"\bhostname\x18\x01 \x01(\tR\bhostname\x12\x18\n" +
|
||||||
|
"\abackend\x18\x02 \x01(\tR\abackend\x12\x12\n" +
|
||||||
|
"\x04mode\x18\x03 \x01(\tR\x04mode\x12\x1f\n" +
|
||||||
|
"\vbackend_tls\x18\x04 \x01(\bR\n" +
|
||||||
|
"backendTls\"\xa7\x01\n" +
|
||||||
|
"\x11ProxyListenerInfo\x12\x12\n" +
|
||||||
|
"\x04addr\x18\x01 \x01(\tR\x04addr\x12\x1f\n" +
|
||||||
|
"\vroute_count\x18\x02 \x01(\x05R\n" +
|
||||||
|
"routeCount\x12-\n" +
|
||||||
|
"\x12active_connections\x18\x03 \x01(\x03R\x11activeConnections\x12.\n" +
|
||||||
|
"\x06routes\x18\x04 \x03(\v2\x16.mcp.v1.ProxyRouteInfoR\x06routes\"\xd4\x01\n" +
|
||||||
|
"\x17ListProxyRoutesResponse\x12\x18\n" +
|
||||||
|
"\aversion\x18\x01 \x01(\tR\aversion\x12+\n" +
|
||||||
|
"\x11total_connections\x18\x02 \x01(\x03R\x10totalConnections\x129\n" +
|
||||||
|
"\n" +
|
||||||
|
"started_at\x18\x03 \x01(\v2\x1a.google.protobuf.TimestampR\tstartedAt\x127\n" +
|
||||||
|
"\tlisteners\x18\x04 \x03(\v2\x19.mcp.v1.ProxyListenerInfoR\tlisteners2\xed\t\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" +
|
||||||
@@ -2387,7 +3014,10 @@ const file_proto_mcp_v1_mcp_proto_rawDesc = "" +
|
|||||||
"\bPushFile\x12\x17.mcp.v1.PushFileRequest\x1a\x18.mcp.v1.PushFileResponse\x12=\n" +
|
"\bPushFile\x12\x17.mcp.v1.PushFileRequest\x1a\x18.mcp.v1.PushFileResponse\x12=\n" +
|
||||||
"\bPullFile\x12\x17.mcp.v1.PullFileRequest\x1a\x18.mcp.v1.PullFileResponse\x12C\n" +
|
"\bPullFile\x12\x17.mcp.v1.PullFileRequest\x1a\x18.mcp.v1.PullFileResponse\x12C\n" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"NodeStatus\x12\x19.mcp.v1.NodeStatusRequest\x1a\x1a.mcp.v1.NodeStatusResponseB*Z(git.wntrmute.dev/mc/mcp/gen/mcp/v1;mcpv1b\x06proto3"
|
"NodeStatus\x12\x19.mcp.v1.NodeStatusRequest\x1a\x1a.mcp.v1.NodeStatusResponse\x12O\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\x123\n" +
|
||||||
|
"\x04Logs\x12\x13.mcp.v1.LogsRequest\x1a\x14.mcp.v1.LogsResponse0\x01B*Z(git.wntrmute.dev/mc/mcp/gen/mcp/v1;mcpv1b\x06proto3"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
file_proto_mcp_v1_mcp_proto_rawDescOnce sync.Once
|
file_proto_mcp_v1_mcp_proto_rawDescOnce sync.Once
|
||||||
@@ -2401,7 +3031,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, 39)
|
var file_proto_mcp_v1_mcp_proto_msgTypes = make([]protoimpl.MessageInfo, 49)
|
||||||
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
|
||||||
@@ -2442,7 +3072,17 @@ var file_proto_mcp_v1_mcp_proto_goTypes = []any{
|
|||||||
(*PurgeRequest)(nil), // 36: mcp.v1.PurgeRequest
|
(*PurgeRequest)(nil), // 36: mcp.v1.PurgeRequest
|
||||||
(*PurgeResponse)(nil), // 37: mcp.v1.PurgeResponse
|
(*PurgeResponse)(nil), // 37: mcp.v1.PurgeResponse
|
||||||
(*PurgeResult)(nil), // 38: mcp.v1.PurgeResult
|
(*PurgeResult)(nil), // 38: mcp.v1.PurgeResult
|
||||||
(*timestamppb.Timestamp)(nil), // 39: google.protobuf.Timestamp
|
(*LogsRequest)(nil), // 39: mcp.v1.LogsRequest
|
||||||
|
(*LogsResponse)(nil), // 40: mcp.v1.LogsResponse
|
||||||
|
(*ListDNSRecordsRequest)(nil), // 41: mcp.v1.ListDNSRecordsRequest
|
||||||
|
(*DNSZone)(nil), // 42: mcp.v1.DNSZone
|
||||||
|
(*DNSRecord)(nil), // 43: mcp.v1.DNSRecord
|
||||||
|
(*ListDNSRecordsResponse)(nil), // 44: mcp.v1.ListDNSRecordsResponse
|
||||||
|
(*ListProxyRoutesRequest)(nil), // 45: mcp.v1.ListProxyRoutesRequest
|
||||||
|
(*ProxyRouteInfo)(nil), // 46: mcp.v1.ProxyRouteInfo
|
||||||
|
(*ProxyListenerInfo)(nil), // 47: mcp.v1.ProxyListenerInfo
|
||||||
|
(*ListProxyRoutesResponse)(nil), // 48: mcp.v1.ListProxyRoutesResponse
|
||||||
|
(*timestamppb.Timestamp)(nil), // 49: 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
|
||||||
@@ -2456,49 +3096,60 @@ 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
|
||||||
39, // 11: mcp.v1.ComponentInfo.started:type_name -> google.protobuf.Timestamp
|
49, // 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
|
||||||
39, // 13: mcp.v1.EventInfo.timestamp:type_name -> google.protobuf.Timestamp
|
49, // 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
|
||||||
39, // 19: mcp.v1.NodeStatusResponse.uptime_since:type_name -> google.protobuf.Timestamp
|
49, // 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
|
||||||
3, // 21: mcp.v1.McpAgentService.Deploy:input_type -> mcp.v1.DeployRequest
|
43, // 21: mcp.v1.DNSZone.records:type_name -> mcp.v1.DNSRecord
|
||||||
12, // 22: mcp.v1.McpAgentService.UndeployService:input_type -> mcp.v1.UndeployServiceRequest
|
42, // 22: mcp.v1.ListDNSRecordsResponse.zones:type_name -> mcp.v1.DNSZone
|
||||||
6, // 23: mcp.v1.McpAgentService.StopService:input_type -> mcp.v1.StopServiceRequest
|
46, // 23: mcp.v1.ProxyListenerInfo.routes:type_name -> mcp.v1.ProxyRouteInfo
|
||||||
8, // 24: mcp.v1.McpAgentService.StartService:input_type -> mcp.v1.StartServiceRequest
|
49, // 24: mcp.v1.ListProxyRoutesResponse.started_at:type_name -> google.protobuf.Timestamp
|
||||||
10, // 25: mcp.v1.McpAgentService.RestartService:input_type -> mcp.v1.RestartServiceRequest
|
47, // 25: mcp.v1.ListProxyRoutesResponse.listeners:type_name -> mcp.v1.ProxyListenerInfo
|
||||||
14, // 26: mcp.v1.McpAgentService.SyncDesiredState:input_type -> mcp.v1.SyncDesiredStateRequest
|
3, // 26: mcp.v1.McpAgentService.Deploy:input_type -> mcp.v1.DeployRequest
|
||||||
17, // 27: mcp.v1.McpAgentService.ListServices:input_type -> mcp.v1.ListServicesRequest
|
12, // 27: mcp.v1.McpAgentService.UndeployService:input_type -> mcp.v1.UndeployServiceRequest
|
||||||
21, // 28: mcp.v1.McpAgentService.GetServiceStatus:input_type -> mcp.v1.GetServiceStatusRequest
|
6, // 28: mcp.v1.McpAgentService.StopService:input_type -> mcp.v1.StopServiceRequest
|
||||||
25, // 29: mcp.v1.McpAgentService.LiveCheck:input_type -> mcp.v1.LiveCheckRequest
|
8, // 29: mcp.v1.McpAgentService.StartService:input_type -> mcp.v1.StartServiceRequest
|
||||||
27, // 30: mcp.v1.McpAgentService.AdoptContainers:input_type -> mcp.v1.AdoptContainersRequest
|
10, // 30: mcp.v1.McpAgentService.RestartService:input_type -> mcp.v1.RestartServiceRequest
|
||||||
36, // 31: mcp.v1.McpAgentService.PurgeComponent:input_type -> mcp.v1.PurgeRequest
|
14, // 31: mcp.v1.McpAgentService.SyncDesiredState:input_type -> mcp.v1.SyncDesiredStateRequest
|
||||||
30, // 32: mcp.v1.McpAgentService.PushFile:input_type -> mcp.v1.PushFileRequest
|
17, // 32: mcp.v1.McpAgentService.ListServices:input_type -> mcp.v1.ListServicesRequest
|
||||||
32, // 33: mcp.v1.McpAgentService.PullFile:input_type -> mcp.v1.PullFileRequest
|
21, // 33: mcp.v1.McpAgentService.GetServiceStatus:input_type -> mcp.v1.GetServiceStatusRequest
|
||||||
34, // 34: mcp.v1.McpAgentService.NodeStatus:input_type -> mcp.v1.NodeStatusRequest
|
25, // 34: mcp.v1.McpAgentService.LiveCheck:input_type -> mcp.v1.LiveCheckRequest
|
||||||
4, // 35: mcp.v1.McpAgentService.Deploy:output_type -> mcp.v1.DeployResponse
|
27, // 35: mcp.v1.McpAgentService.AdoptContainers:input_type -> mcp.v1.AdoptContainersRequest
|
||||||
13, // 36: mcp.v1.McpAgentService.UndeployService:output_type -> mcp.v1.UndeployServiceResponse
|
36, // 36: mcp.v1.McpAgentService.PurgeComponent:input_type -> mcp.v1.PurgeRequest
|
||||||
7, // 37: mcp.v1.McpAgentService.StopService:output_type -> mcp.v1.StopServiceResponse
|
30, // 37: mcp.v1.McpAgentService.PushFile:input_type -> mcp.v1.PushFileRequest
|
||||||
9, // 38: mcp.v1.McpAgentService.StartService:output_type -> mcp.v1.StartServiceResponse
|
32, // 38: mcp.v1.McpAgentService.PullFile:input_type -> mcp.v1.PullFileRequest
|
||||||
11, // 39: mcp.v1.McpAgentService.RestartService:output_type -> mcp.v1.RestartServiceResponse
|
34, // 39: mcp.v1.McpAgentService.NodeStatus:input_type -> mcp.v1.NodeStatusRequest
|
||||||
15, // 40: mcp.v1.McpAgentService.SyncDesiredState:output_type -> mcp.v1.SyncDesiredStateResponse
|
41, // 40: mcp.v1.McpAgentService.ListDNSRecords:input_type -> mcp.v1.ListDNSRecordsRequest
|
||||||
20, // 41: mcp.v1.McpAgentService.ListServices:output_type -> mcp.v1.ListServicesResponse
|
45, // 41: mcp.v1.McpAgentService.ListProxyRoutes:input_type -> mcp.v1.ListProxyRoutesRequest
|
||||||
24, // 42: mcp.v1.McpAgentService.GetServiceStatus:output_type -> mcp.v1.GetServiceStatusResponse
|
39, // 42: mcp.v1.McpAgentService.Logs:input_type -> mcp.v1.LogsRequest
|
||||||
26, // 43: mcp.v1.McpAgentService.LiveCheck:output_type -> mcp.v1.LiveCheckResponse
|
4, // 43: mcp.v1.McpAgentService.Deploy:output_type -> mcp.v1.DeployResponse
|
||||||
29, // 44: mcp.v1.McpAgentService.AdoptContainers:output_type -> mcp.v1.AdoptContainersResponse
|
13, // 44: mcp.v1.McpAgentService.UndeployService:output_type -> mcp.v1.UndeployServiceResponse
|
||||||
37, // 45: mcp.v1.McpAgentService.PurgeComponent:output_type -> mcp.v1.PurgeResponse
|
7, // 45: mcp.v1.McpAgentService.StopService:output_type -> mcp.v1.StopServiceResponse
|
||||||
31, // 46: mcp.v1.McpAgentService.PushFile:output_type -> mcp.v1.PushFileResponse
|
9, // 46: mcp.v1.McpAgentService.StartService:output_type -> mcp.v1.StartServiceResponse
|
||||||
33, // 47: mcp.v1.McpAgentService.PullFile:output_type -> mcp.v1.PullFileResponse
|
11, // 47: mcp.v1.McpAgentService.RestartService:output_type -> mcp.v1.RestartServiceResponse
|
||||||
35, // 48: mcp.v1.McpAgentService.NodeStatus:output_type -> mcp.v1.NodeStatusResponse
|
15, // 48: mcp.v1.McpAgentService.SyncDesiredState:output_type -> mcp.v1.SyncDesiredStateResponse
|
||||||
35, // [35:49] is the sub-list for method output_type
|
20, // 49: mcp.v1.McpAgentService.ListServices:output_type -> mcp.v1.ListServicesResponse
|
||||||
21, // [21:35] is the sub-list for method input_type
|
24, // 50: mcp.v1.McpAgentService.GetServiceStatus:output_type -> mcp.v1.GetServiceStatusResponse
|
||||||
21, // [21:21] is the sub-list for extension type_name
|
26, // 51: mcp.v1.McpAgentService.LiveCheck:output_type -> mcp.v1.LiveCheckResponse
|
||||||
21, // [21:21] is the sub-list for extension extendee
|
29, // 52: mcp.v1.McpAgentService.AdoptContainers:output_type -> mcp.v1.AdoptContainersResponse
|
||||||
0, // [0:21] is the sub-list for field type_name
|
37, // 53: mcp.v1.McpAgentService.PurgeComponent:output_type -> mcp.v1.PurgeResponse
|
||||||
|
31, // 54: mcp.v1.McpAgentService.PushFile:output_type -> mcp.v1.PushFileResponse
|
||||||
|
33, // 55: mcp.v1.McpAgentService.PullFile:output_type -> mcp.v1.PullFileResponse
|
||||||
|
35, // 56: mcp.v1.McpAgentService.NodeStatus:output_type -> mcp.v1.NodeStatusResponse
|
||||||
|
44, // 57: mcp.v1.McpAgentService.ListDNSRecords:output_type -> mcp.v1.ListDNSRecordsResponse
|
||||||
|
48, // 58: mcp.v1.McpAgentService.ListProxyRoutes:output_type -> mcp.v1.ListProxyRoutesResponse
|
||||||
|
40, // 59: mcp.v1.McpAgentService.Logs:output_type -> mcp.v1.LogsResponse
|
||||||
|
43, // [43:60] is the sub-list for method output_type
|
||||||
|
26, // [26:43] is the sub-list for method input_type
|
||||||
|
26, // [26:26] is the sub-list for extension type_name
|
||||||
|
26, // [26:26] is the sub-list for extension extendee
|
||||||
|
0, // [0:26] is the sub-list for field type_name
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() { file_proto_mcp_v1_mcp_proto_init() }
|
func init() { file_proto_mcp_v1_mcp_proto_init() }
|
||||||
@@ -2512,7 +3163,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: 39,
|
NumMessages: 49,
|
||||||
NumExtensions: 0,
|
NumExtensions: 0,
|
||||||
NumServices: 1,
|
NumServices: 1,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -33,6 +33,9 @@ const (
|
|||||||
McpAgentService_PushFile_FullMethodName = "/mcp.v1.McpAgentService/PushFile"
|
McpAgentService_PushFile_FullMethodName = "/mcp.v1.McpAgentService/PushFile"
|
||||||
McpAgentService_PullFile_FullMethodName = "/mcp.v1.McpAgentService/PullFile"
|
McpAgentService_PullFile_FullMethodName = "/mcp.v1.McpAgentService/PullFile"
|
||||||
McpAgentService_NodeStatus_FullMethodName = "/mcp.v1.McpAgentService/NodeStatus"
|
McpAgentService_NodeStatus_FullMethodName = "/mcp.v1.McpAgentService/NodeStatus"
|
||||||
|
McpAgentService_ListDNSRecords_FullMethodName = "/mcp.v1.McpAgentService/ListDNSRecords"
|
||||||
|
McpAgentService_ListProxyRoutes_FullMethodName = "/mcp.v1.McpAgentService/ListProxyRoutes"
|
||||||
|
McpAgentService_Logs_FullMethodName = "/mcp.v1.McpAgentService/Logs"
|
||||||
)
|
)
|
||||||
|
|
||||||
// McpAgentServiceClient is the client API for McpAgentService service.
|
// McpAgentServiceClient is the client API for McpAgentService service.
|
||||||
@@ -60,6 +63,12 @@ type McpAgentServiceClient interface {
|
|||||||
PullFile(ctx context.Context, in *PullFileRequest, opts ...grpc.CallOption) (*PullFileResponse, error)
|
PullFile(ctx context.Context, in *PullFileRequest, opts ...grpc.CallOption) (*PullFileResponse, error)
|
||||||
// Node
|
// Node
|
||||||
NodeStatus(ctx context.Context, in *NodeStatusRequest, opts ...grpc.CallOption) (*NodeStatusResponse, error)
|
NodeStatus(ctx context.Context, in *NodeStatusRequest, opts ...grpc.CallOption) (*NodeStatusResponse, error)
|
||||||
|
// DNS (query MCNS)
|
||||||
|
ListDNSRecords(ctx context.Context, in *ListDNSRecordsRequest, opts ...grpc.CallOption) (*ListDNSRecordsResponse, error)
|
||||||
|
// Proxy routes (query mc-proxy)
|
||||||
|
ListProxyRoutes(ctx context.Context, in *ListProxyRoutesRequest, opts ...grpc.CallOption) (*ListProxyRoutesResponse, error)
|
||||||
|
// Logs
|
||||||
|
Logs(ctx context.Context, in *LogsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[LogsResponse], error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type mcpAgentServiceClient struct {
|
type mcpAgentServiceClient struct {
|
||||||
@@ -210,6 +219,45 @@ func (c *mcpAgentServiceClient) NodeStatus(ctx context.Context, in *NodeStatusRe
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *mcpAgentServiceClient) ListDNSRecords(ctx context.Context, in *ListDNSRecordsRequest, opts ...grpc.CallOption) (*ListDNSRecordsResponse, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(ListDNSRecordsResponse)
|
||||||
|
err := c.cc.Invoke(ctx, McpAgentService_ListDNSRecords_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *mcpAgentServiceClient) ListProxyRoutes(ctx context.Context, in *ListProxyRoutesRequest, opts ...grpc.CallOption) (*ListProxyRoutesResponse, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(ListProxyRoutesResponse)
|
||||||
|
err := c.cc.Invoke(ctx, McpAgentService_ListProxyRoutes_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *mcpAgentServiceClient) Logs(ctx context.Context, in *LogsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[LogsResponse], error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
stream, err := c.cc.NewStream(ctx, &McpAgentService_ServiceDesc.Streams[0], McpAgentService_Logs_FullMethodName, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
x := &grpc.GenericClientStream[LogsRequest, LogsResponse]{ClientStream: stream}
|
||||||
|
if err := x.ClientStream.SendMsg(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := x.ClientStream.CloseSend(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return x, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||||
|
type McpAgentService_LogsClient = grpc.ServerStreamingClient[LogsResponse]
|
||||||
|
|
||||||
// McpAgentServiceServer is the server API for McpAgentService service.
|
// McpAgentServiceServer is the server API for McpAgentService service.
|
||||||
// All implementations must embed UnimplementedMcpAgentServiceServer
|
// All implementations must embed UnimplementedMcpAgentServiceServer
|
||||||
// for forward compatibility.
|
// for forward compatibility.
|
||||||
@@ -235,6 +283,12 @@ type McpAgentServiceServer interface {
|
|||||||
PullFile(context.Context, *PullFileRequest) (*PullFileResponse, error)
|
PullFile(context.Context, *PullFileRequest) (*PullFileResponse, error)
|
||||||
// Node
|
// Node
|
||||||
NodeStatus(context.Context, *NodeStatusRequest) (*NodeStatusResponse, error)
|
NodeStatus(context.Context, *NodeStatusRequest) (*NodeStatusResponse, error)
|
||||||
|
// DNS (query MCNS)
|
||||||
|
ListDNSRecords(context.Context, *ListDNSRecordsRequest) (*ListDNSRecordsResponse, error)
|
||||||
|
// Proxy routes (query mc-proxy)
|
||||||
|
ListProxyRoutes(context.Context, *ListProxyRoutesRequest) (*ListProxyRoutesResponse, error)
|
||||||
|
// Logs
|
||||||
|
Logs(*LogsRequest, grpc.ServerStreamingServer[LogsResponse]) error
|
||||||
mustEmbedUnimplementedMcpAgentServiceServer()
|
mustEmbedUnimplementedMcpAgentServiceServer()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -287,6 +341,15 @@ func (UnimplementedMcpAgentServiceServer) PullFile(context.Context, *PullFileReq
|
|||||||
func (UnimplementedMcpAgentServiceServer) NodeStatus(context.Context, *NodeStatusRequest) (*NodeStatusResponse, error) {
|
func (UnimplementedMcpAgentServiceServer) NodeStatus(context.Context, *NodeStatusRequest) (*NodeStatusResponse, error) {
|
||||||
return nil, status.Error(codes.Unimplemented, "method NodeStatus not implemented")
|
return nil, status.Error(codes.Unimplemented, "method NodeStatus not implemented")
|
||||||
}
|
}
|
||||||
|
func (UnimplementedMcpAgentServiceServer) ListDNSRecords(context.Context, *ListDNSRecordsRequest) (*ListDNSRecordsResponse, error) {
|
||||||
|
return nil, status.Error(codes.Unimplemented, "method ListDNSRecords not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedMcpAgentServiceServer) ListProxyRoutes(context.Context, *ListProxyRoutesRequest) (*ListProxyRoutesResponse, error) {
|
||||||
|
return nil, status.Error(codes.Unimplemented, "method ListProxyRoutes not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedMcpAgentServiceServer) Logs(*LogsRequest, grpc.ServerStreamingServer[LogsResponse]) error {
|
||||||
|
return status.Error(codes.Unimplemented, "method Logs not implemented")
|
||||||
|
}
|
||||||
func (UnimplementedMcpAgentServiceServer) mustEmbedUnimplementedMcpAgentServiceServer() {}
|
func (UnimplementedMcpAgentServiceServer) mustEmbedUnimplementedMcpAgentServiceServer() {}
|
||||||
func (UnimplementedMcpAgentServiceServer) testEmbeddedByValue() {}
|
func (UnimplementedMcpAgentServiceServer) testEmbeddedByValue() {}
|
||||||
|
|
||||||
@@ -560,6 +623,53 @@ func _McpAgentService_NodeStatus_Handler(srv interface{}, ctx context.Context, d
|
|||||||
return interceptor(ctx, in, info, handler)
|
return interceptor(ctx, in, info, handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func _McpAgentService_ListDNSRecords_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(ListDNSRecordsRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(McpAgentServiceServer).ListDNSRecords(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: McpAgentService_ListDNSRecords_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(McpAgentServiceServer).ListDNSRecords(ctx, req.(*ListDNSRecordsRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _McpAgentService_ListProxyRoutes_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(ListProxyRoutesRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(McpAgentServiceServer).ListProxyRoutes(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: McpAgentService_ListProxyRoutes_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(McpAgentServiceServer).ListProxyRoutes(ctx, req.(*ListProxyRoutesRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _McpAgentService_Logs_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||||
|
m := new(LogsRequest)
|
||||||
|
if err := stream.RecvMsg(m); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return srv.(McpAgentServiceServer).Logs(m, &grpc.GenericServerStream[LogsRequest, LogsResponse]{ServerStream: stream})
|
||||||
|
}
|
||||||
|
|
||||||
|
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||||
|
type McpAgentService_LogsServer = grpc.ServerStreamingServer[LogsResponse]
|
||||||
|
|
||||||
// McpAgentService_ServiceDesc is the grpc.ServiceDesc for McpAgentService service.
|
// McpAgentService_ServiceDesc is the grpc.ServiceDesc for McpAgentService service.
|
||||||
// It's only intended for direct use with grpc.RegisterService,
|
// It's only intended for direct use with grpc.RegisterService,
|
||||||
// and not to be introspected or modified (even as a copy)
|
// and not to be introspected or modified (even as a copy)
|
||||||
@@ -623,7 +733,21 @@ var McpAgentService_ServiceDesc = grpc.ServiceDesc{
|
|||||||
MethodName: "NodeStatus",
|
MethodName: "NodeStatus",
|
||||||
Handler: _McpAgentService_NodeStatus_Handler,
|
Handler: _McpAgentService_NodeStatus_Handler,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
MethodName: "ListDNSRecords",
|
||||||
|
Handler: _McpAgentService_ListDNSRecords_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "ListProxyRoutes",
|
||||||
|
Handler: _McpAgentService_ListProxyRoutes_Handler,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Streams: []grpc.StreamDesc{
|
||||||
|
{
|
||||||
|
StreamName: "Logs",
|
||||||
|
Handler: _McpAgentService_Logs_Handler,
|
||||||
|
ServerStreams: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Streams: []grpc.StreamDesc{},
|
|
||||||
Metadata: "proto/mcp/v1/mcp.proto",
|
Metadata: "proto/mcp/v1/mcp.proto",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,11 +35,12 @@ type Agent struct {
|
|||||||
Proxy *ProxyRouter
|
Proxy *ProxyRouter
|
||||||
Certs *CertProvisioner
|
Certs *CertProvisioner
|
||||||
DNS *DNSRegistrar
|
DNS *DNSRegistrar
|
||||||
|
Version string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run starts the agent: opens the database, sets up the gRPC server with
|
// Run starts the agent: opens the database, sets up the gRPC server with
|
||||||
// TLS and auth, and blocks until SIGINT/SIGTERM.
|
// TLS and auth, and blocks until SIGINT/SIGTERM.
|
||||||
func Run(cfg *config.AgentConfig) error {
|
func Run(cfg *config.AgentConfig, version string) error {
|
||||||
logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
|
logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
|
||||||
Level: parseLogLevel(cfg.Log.Level),
|
Level: parseLogLevel(cfg.Log.Level),
|
||||||
}))
|
}))
|
||||||
@@ -79,6 +80,7 @@ func Run(cfg *config.AgentConfig) error {
|
|||||||
Proxy: proxy,
|
Proxy: proxy,
|
||||||
Certs: certs,
|
Certs: certs,
|
||||||
DNS: dns,
|
DNS: dns,
|
||||||
|
Version: version,
|
||||||
}
|
}
|
||||||
|
|
||||||
tlsCert, err := tls.LoadX509KeyPair(cfg.Server.TLSCert, cfg.Server.TLSKey)
|
tlsCert, err := tls.LoadX509KeyPair(cfg.Server.TLSCert, cfg.Server.TLSKey)
|
||||||
@@ -100,6 +102,9 @@ func Run(cfg *config.AgentConfig) error {
|
|||||||
grpc.ChainUnaryInterceptor(
|
grpc.ChainUnaryInterceptor(
|
||||||
auth.AuthInterceptor(validator),
|
auth.AuthInterceptor(validator),
|
||||||
),
|
),
|
||||||
|
grpc.ChainStreamInterceptor(
|
||||||
|
auth.StreamAuthInterceptor(validator),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
mcpv1.RegisterMcpAgentServiceServer(server, a)
|
mcpv1.RegisterMcpAgentServiceServer(server, a)
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,9 @@ func (a *Agent) Deploy(ctx context.Context, req *mcpv1.DeployRequest) (*mcpv1.De
|
|||||||
filtered = append(filtered, cs)
|
filtered = append(filtered, cs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if len(filtered) == 0 {
|
||||||
|
return nil, fmt.Errorf("component %q not found in service %q", target, serviceName)
|
||||||
|
}
|
||||||
components = filtered
|
components = filtered
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ type DNSRegistrar struct {
|
|||||||
logger *slog.Logger
|
logger *slog.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
// dnsRecord is the JSON representation of an MCNS record.
|
// DNSRecord is the JSON representation of an MCNS record.
|
||||||
type dnsRecord struct {
|
type DNSRecord struct {
|
||||||
ID int `json:"ID"`
|
ID int `json:"ID"`
|
||||||
Name string `json:"Name"`
|
Name string `json:"Name"`
|
||||||
Type string `json:"Type"`
|
Type string `json:"Type"`
|
||||||
@@ -136,8 +136,87 @@ func (d *DNSRegistrar) RemoveRecord(ctx context.Context, serviceName string) err
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DNSZone is the JSON representation of an MCNS zone.
|
||||||
|
type DNSZone struct {
|
||||||
|
Name string `json:"Name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListZones returns all zones from MCNS.
|
||||||
|
func (d *DNSRegistrar) ListZones(ctx context.Context) ([]DNSZone, error) {
|
||||||
|
if d == nil {
|
||||||
|
return nil, fmt.Errorf("DNS registrar not configured")
|
||||||
|
}
|
||||||
|
|
||||||
|
url := fmt.Sprintf("%s/v1/zones", d.serverURL)
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("create list zones request: %w", err)
|
||||||
|
}
|
||||||
|
req.Header.Set("Authorization", "Bearer "+d.token)
|
||||||
|
|
||||||
|
resp, err := d.httpClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("list zones: %w", err)
|
||||||
|
}
|
||||||
|
defer func() { _ = resp.Body.Close() }()
|
||||||
|
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("read list zones response: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return nil, fmt.Errorf("list zones: mcns returned %d: %s", resp.StatusCode, string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
var envelope struct {
|
||||||
|
Zones []DNSZone `json:"zones"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(body, &envelope); err != nil {
|
||||||
|
return nil, fmt.Errorf("parse list zones response: %w", err)
|
||||||
|
}
|
||||||
|
return envelope.Zones, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListZoneRecords returns all records in the given zone (no filters).
|
||||||
|
func (d *DNSRegistrar) ListZoneRecords(ctx context.Context, zone string) ([]DNSRecord, error) {
|
||||||
|
if d == nil {
|
||||||
|
return nil, fmt.Errorf("DNS registrar not configured")
|
||||||
|
}
|
||||||
|
|
||||||
|
url := fmt.Sprintf("%s/v1/zones/%s/records", d.serverURL, zone)
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("create list zone records request: %w", err)
|
||||||
|
}
|
||||||
|
req.Header.Set("Authorization", "Bearer "+d.token)
|
||||||
|
|
||||||
|
resp, err := d.httpClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("list zone records: %w", err)
|
||||||
|
}
|
||||||
|
defer func() { _ = resp.Body.Close() }()
|
||||||
|
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("read list zone records response: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return nil, fmt.Errorf("list zone records: mcns returned %d: %s", resp.StatusCode, string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
var envelope struct {
|
||||||
|
Records []DNSRecord `json:"records"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(body, &envelope); err != nil {
|
||||||
|
return nil, fmt.Errorf("parse list zone records response: %w", err)
|
||||||
|
}
|
||||||
|
return envelope.Records, nil
|
||||||
|
}
|
||||||
|
|
||||||
// listRecords returns A records matching the service name in the zone.
|
// listRecords returns A records matching the service name in the zone.
|
||||||
func (d *DNSRegistrar) listRecords(ctx context.Context, serviceName string) ([]dnsRecord, error) {
|
func (d *DNSRegistrar) listRecords(ctx context.Context, serviceName string) ([]DNSRecord, error) {
|
||||||
url := fmt.Sprintf("%s/v1/zones/%s/records?name=%s&type=A", d.serverURL, d.zone, serviceName)
|
url := fmt.Sprintf("%s/v1/zones/%s/records?name=%s&type=A", d.serverURL, d.zone, serviceName)
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -161,7 +240,7 @@ func (d *DNSRegistrar) listRecords(ctx context.Context, serviceName string) ([]d
|
|||||||
}
|
}
|
||||||
|
|
||||||
var envelope struct {
|
var envelope struct {
|
||||||
Records []dnsRecord `json:"records"`
|
Records []DNSRecord `json:"records"`
|
||||||
}
|
}
|
||||||
if err := json.Unmarshal(body, &envelope); err != nil {
|
if err := json.Unmarshal(body, &envelope); err != nil {
|
||||||
return nil, fmt.Errorf("parse list response: %w", err)
|
return nil, fmt.Errorf("parse list response: %w", err)
|
||||||
|
|||||||
40
internal/agent/dns_rpc.go
Normal file
40
internal/agent/dns_rpc.go
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
package agent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
mcpv1 "git.wntrmute.dev/mc/mcp/gen/mcp/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ListDNSRecords queries MCNS for all zones and their records.
|
||||||
|
func (a *Agent) ListDNSRecords(ctx context.Context, _ *mcpv1.ListDNSRecordsRequest) (*mcpv1.ListDNSRecordsResponse, error) {
|
||||||
|
a.Logger.Debug("ListDNSRecords called")
|
||||||
|
|
||||||
|
zones, err := a.DNS.ListZones(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("list zones: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := &mcpv1.ListDNSRecordsResponse{}
|
||||||
|
for _, z := range zones {
|
||||||
|
records, err := a.DNS.ListZoneRecords(ctx, z.Name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("list records for zone %q: %w", z.Name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
zone := &mcpv1.DNSZone{Name: z.Name}
|
||||||
|
for _, r := range records {
|
||||||
|
zone.Records = append(zone.Records, &mcpv1.DNSRecord{
|
||||||
|
Id: int64(r.ID),
|
||||||
|
Name: r.Name,
|
||||||
|
Type: r.Type,
|
||||||
|
Value: r.Value,
|
||||||
|
Ttl: int32(r.TTL), //nolint:gosec // TTL is bounded
|
||||||
|
})
|
||||||
|
}
|
||||||
|
resp.Zones = append(resp.Zones, zone)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
@@ -90,7 +90,7 @@ func TestEnsureRecordSkipsWhenExists(t *testing.T) {
|
|||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method == http.MethodGet {
|
if r.Method == http.MethodGet {
|
||||||
// Return an existing record with the correct value.
|
// Return an existing record with the correct value.
|
||||||
resp := map[string][]dnsRecord{"records": {{ID: 1, Name: "myservice", Type: "A", Value: "192.168.88.181", TTL: 300}}}
|
resp := map[string][]DNSRecord{"records": {{ID: 1, Name: "myservice", Type: "A", Value: "192.168.88.181", TTL: 300}}}
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
_ = json.NewEncoder(w).Encode(resp)
|
_ = json.NewEncoder(w).Encode(resp)
|
||||||
return
|
return
|
||||||
@@ -124,7 +124,7 @@ func TestEnsureRecordUpdatesWrongValue(t *testing.T) {
|
|||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method == http.MethodGet {
|
if r.Method == http.MethodGet {
|
||||||
// Return a record with a stale value.
|
// Return a record with a stale value.
|
||||||
resp := map[string][]dnsRecord{"records": {{ID: 42, Name: "myservice", Type: "A", Value: "10.0.0.1", TTL: 300}}}
|
resp := map[string][]DNSRecord{"records": {{ID: 42, Name: "myservice", Type: "A", Value: "10.0.0.1", TTL: 300}}}
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
_ = json.NewEncoder(w).Encode(resp)
|
_ = json.NewEncoder(w).Encode(resp)
|
||||||
return
|
return
|
||||||
@@ -160,7 +160,7 @@ func TestRemoveRecordDeletes(t *testing.T) {
|
|||||||
|
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method == http.MethodGet {
|
if r.Method == http.MethodGet {
|
||||||
resp := map[string][]dnsRecord{"records": {{ID: 7, Name: "myservice", Type: "A", Value: "192.168.88.181", TTL: 300}}}
|
resp := map[string][]DNSRecord{"records": {{ID: 7, Name: "myservice", Type: "A", Value: "192.168.88.181", TTL: 300}}}
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
_ = json.NewEncoder(w).Encode(resp)
|
_ = json.NewEncoder(w).Encode(resp)
|
||||||
return
|
return
|
||||||
|
|||||||
79
internal/agent/logs.go
Normal file
79
internal/agent/logs.go
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
package agent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
mcpv1 "git.wntrmute.dev/mc/mcp/gen/mcp/v1"
|
||||||
|
"git.wntrmute.dev/mc/mcp/internal/registry"
|
||||||
|
"git.wntrmute.dev/mc/mcp/internal/runtime"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Logs streams container logs for a service component.
|
||||||
|
func (a *Agent) Logs(req *mcpv1.LogsRequest, stream mcpv1.McpAgentService_LogsServer) error {
|
||||||
|
if req.GetService() == "" {
|
||||||
|
return status.Error(codes.InvalidArgument, "service name is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve component name.
|
||||||
|
component := req.GetComponent()
|
||||||
|
if component == "" {
|
||||||
|
components, err := registry.ListComponents(a.DB, req.GetService())
|
||||||
|
if err != nil {
|
||||||
|
return status.Errorf(codes.Internal, "list components: %v", err)
|
||||||
|
}
|
||||||
|
if len(components) == 0 {
|
||||||
|
return status.Error(codes.NotFound, "no components found for service")
|
||||||
|
}
|
||||||
|
component = components[0].Name
|
||||||
|
}
|
||||||
|
|
||||||
|
containerName := ContainerNameFor(req.GetService(), component)
|
||||||
|
|
||||||
|
podman, ok := a.Runtime.(*runtime.Podman)
|
||||||
|
if !ok {
|
||||||
|
return status.Error(codes.Internal, "logs requires podman runtime")
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := podman.Logs(stream.Context(), containerName, int(req.GetTail()), req.GetFollow(), req.GetTimestamps(), req.GetSince())
|
||||||
|
|
||||||
|
a.Logger.Info("running podman logs", "container", containerName, "args", cmd.Args)
|
||||||
|
|
||||||
|
// Podman writes container stdout to its stdout and container stderr
|
||||||
|
// to its stderr. Merge both into a single pipe.
|
||||||
|
pr, pw := io.Pipe()
|
||||||
|
cmd.Stdout = pw
|
||||||
|
cmd.Stderr = pw
|
||||||
|
|
||||||
|
if err := cmd.Start(); err != nil {
|
||||||
|
pw.Close()
|
||||||
|
return status.Errorf(codes.Internal, "start podman logs: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close the write end when the command exits so the scanner finishes.
|
||||||
|
go func() {
|
||||||
|
err := cmd.Wait()
|
||||||
|
if err != nil {
|
||||||
|
a.Logger.Warn("podman logs exited", "container", containerName, "error", err)
|
||||||
|
}
|
||||||
|
pw.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(pr)
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := scanner.Bytes()
|
||||||
|
if len(line) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := stream.Send(&mcpv1.LogsResponse{
|
||||||
|
Data: append(line, '\n'),
|
||||||
|
}); err != nil {
|
||||||
|
_ = cmd.Process.Kill()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -31,6 +31,7 @@ func (a *Agent) NodeStatus(ctx context.Context, _ *mcpv1.NodeStatusRequest) (*mc
|
|||||||
Runtime: a.Config.Agent.ContainerRuntime,
|
Runtime: a.Config.Agent.ContainerRuntime,
|
||||||
ServiceCount: uint32(len(services)), //nolint:gosec // bounded
|
ServiceCount: uint32(len(services)), //nolint:gosec // bounded
|
||||||
ComponentCount: componentCount,
|
ComponentCount: componentCount,
|
||||||
|
AgentVersion: a.Version,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Runtime version.
|
// Runtime version.
|
||||||
|
|||||||
@@ -48,6 +48,14 @@ func (p *ProxyRouter) Close() error {
|
|||||||
return p.client.Close()
|
return p.client.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetStatus returns the mc-proxy server status.
|
||||||
|
func (p *ProxyRouter) GetStatus(ctx context.Context) (*mcproxy.Status, error) {
|
||||||
|
if p == nil {
|
||||||
|
return nil, fmt.Errorf("mc-proxy not configured")
|
||||||
|
}
|
||||||
|
return p.client.GetStatus(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
// RegisterRoutes registers all routes for a service component with mc-proxy.
|
// RegisterRoutes registers all routes for a service component with mc-proxy.
|
||||||
// It uses the assigned host ports from the registry.
|
// It uses the assigned host ports from the registry.
|
||||||
func (p *ProxyRouter) RegisterRoutes(ctx context.Context, serviceName string, routes []registry.Route, hostPorts map[string]int) error {
|
func (p *ProxyRouter) RegisterRoutes(ctx context.Context, serviceName string, routes []registry.Route, hostPorts map[string]int) error {
|
||||||
|
|||||||
46
internal/agent/proxy_rpc.go
Normal file
46
internal/agent/proxy_rpc.go
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
package agent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
mcpv1 "git.wntrmute.dev/mc/mcp/gen/mcp/v1"
|
||||||
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ListProxyRoutes queries mc-proxy for its current status and routes.
|
||||||
|
func (a *Agent) ListProxyRoutes(ctx context.Context, _ *mcpv1.ListProxyRoutesRequest) (*mcpv1.ListProxyRoutesResponse, error) {
|
||||||
|
a.Logger.Debug("ListProxyRoutes called")
|
||||||
|
|
||||||
|
status, err := a.Proxy.GetStatus(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("get mc-proxy status: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := &mcpv1.ListProxyRoutesResponse{
|
||||||
|
Version: status.Version,
|
||||||
|
TotalConnections: status.TotalConnections,
|
||||||
|
}
|
||||||
|
if !status.StartedAt.IsZero() {
|
||||||
|
resp.StartedAt = timestamppb.New(status.StartedAt)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ls := range status.Listeners {
|
||||||
|
listener := &mcpv1.ProxyListenerInfo{
|
||||||
|
Addr: ls.Addr,
|
||||||
|
RouteCount: int32(ls.RouteCount), //nolint:gosec // bounded
|
||||||
|
ActiveConnections: ls.ActiveConnections,
|
||||||
|
}
|
||||||
|
for _, r := range ls.Routes {
|
||||||
|
listener.Routes = append(listener.Routes, &mcpv1.ProxyRouteInfo{
|
||||||
|
Hostname: r.Hostname,
|
||||||
|
Backend: r.Backend,
|
||||||
|
Mode: r.Mode,
|
||||||
|
BackendTls: r.BackendTLS,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
resp.Listeners = append(resp.Listeners, listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
@@ -99,6 +99,12 @@ func (a *Agent) liveCheckServices(ctx context.Context) ([]*mcpv1.ServiceInfo, er
|
|||||||
|
|
||||||
if rc, ok := runtimeByName[containerName]; ok {
|
if rc, ok := runtimeByName[containerName]; ok {
|
||||||
ci.ObservedState = rc.State
|
ci.ObservedState = rc.State
|
||||||
|
if rc.Version != "" {
|
||||||
|
ci.Version = rc.Version
|
||||||
|
}
|
||||||
|
if rc.Image != "" {
|
||||||
|
ci.Image = rc.Image
|
||||||
|
}
|
||||||
if !rc.Started.IsZero() {
|
if !rc.Started.IsZero() {
|
||||||
ci.Started = timestamppb.New(rc.Started)
|
ci.Started = timestamppb.New(rc.Started)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -255,6 +255,52 @@ func AuthInterceptor(validator TokenValidator) grpc.UnaryServerInterceptor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StreamAuthInterceptor returns a gRPC stream server interceptor with
|
||||||
|
// the same authentication rules as AuthInterceptor.
|
||||||
|
func StreamAuthInterceptor(validator TokenValidator) grpc.StreamServerInterceptor {
|
||||||
|
return func(
|
||||||
|
srv any,
|
||||||
|
ss grpc.ServerStream,
|
||||||
|
info *grpc.StreamServerInfo,
|
||||||
|
handler grpc.StreamHandler,
|
||||||
|
) error {
|
||||||
|
md, ok := metadata.FromIncomingContext(ss.Context())
|
||||||
|
if !ok {
|
||||||
|
return status.Error(codes.Unauthenticated, "missing metadata")
|
||||||
|
}
|
||||||
|
|
||||||
|
authValues := md.Get("authorization")
|
||||||
|
if len(authValues) == 0 {
|
||||||
|
return status.Error(codes.Unauthenticated, "missing authorization header")
|
||||||
|
}
|
||||||
|
|
||||||
|
authHeader := authValues[0]
|
||||||
|
if !strings.HasPrefix(authHeader, "Bearer ") {
|
||||||
|
return status.Error(codes.Unauthenticated, "malformed authorization header")
|
||||||
|
}
|
||||||
|
token := strings.TrimPrefix(authHeader, "Bearer ")
|
||||||
|
|
||||||
|
tokenInfo, err := validator.ValidateToken(ss.Context(), token)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("token validation failed", "method", info.FullMethod, "error", err)
|
||||||
|
return status.Error(codes.Unauthenticated, "token validation failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !tokenInfo.Valid {
|
||||||
|
return status.Error(codes.Unauthenticated, "invalid token")
|
||||||
|
}
|
||||||
|
|
||||||
|
if tokenInfo.HasRole("guest") {
|
||||||
|
slog.Warn("guest access denied", "method", info.FullMethod, "user", tokenInfo.Username)
|
||||||
|
return status.Error(codes.PermissionDenied, "guest access not permitted")
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.Info("rpc", "method", info.FullMethod, "user", tokenInfo.Username, "account_type", tokenInfo.AccountType)
|
||||||
|
|
||||||
|
return handler(srv, ss)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Login authenticates with MCIAS and returns a bearer token.
|
// Login authenticates with MCIAS and returns a bearer token.
|
||||||
func Login(serverURL, caCertPath, username, password string) (string, error) {
|
func Login(serverURL, caCertPath, username, password string) (string, error) {
|
||||||
client, err := newHTTPClient(caCertPath)
|
client, err := newHTTPClient(caCertPath)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -178,6 +179,71 @@ func (p *Podman) Inspect(ctx context.Context, name string) (ContainerInfo, error
|
|||||||
return info, nil
|
return info, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Logs returns an exec.Cmd that streams container logs. For containers
|
||||||
|
// using the journald log driver, it tries journalctl first (podman logs
|
||||||
|
// can't read journald outside the originating user session). If journalctl
|
||||||
|
// can't access the journal, it falls back to podman logs.
|
||||||
|
func (p *Podman) Logs(ctx context.Context, containerName string, tail int, follow, timestamps bool, since string) *exec.Cmd {
|
||||||
|
// Check if this container uses the journald log driver.
|
||||||
|
inspectCmd := exec.CommandContext(ctx, p.command(), "inspect", "--format", "{{.HostConfig.LogConfig.Type}}", containerName) //nolint:gosec
|
||||||
|
if out, err := inspectCmd.Output(); err == nil && strings.TrimSpace(string(out)) == "journald" {
|
||||||
|
if p.journalAccessible(ctx, containerName) {
|
||||||
|
return p.journalLogs(ctx, containerName, tail, follow, since)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.podmanLogs(ctx, containerName, tail, follow, timestamps, since)
|
||||||
|
}
|
||||||
|
|
||||||
|
// journalAccessible probes whether journalctl can read logs for the container.
|
||||||
|
func (p *Podman) journalAccessible(ctx context.Context, containerName string) bool {
|
||||||
|
args := []string{"--no-pager", "-n", "0"}
|
||||||
|
if os.Getuid() != 0 {
|
||||||
|
args = append(args, "--user")
|
||||||
|
}
|
||||||
|
args = append(args, "CONTAINER_NAME="+containerName)
|
||||||
|
cmd := exec.CommandContext(ctx, "journalctl", args...) //nolint:gosec
|
||||||
|
return cmd.Run() == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// journalLogs returns a journalctl command filtered by container name.
|
||||||
|
func (p *Podman) journalLogs(ctx context.Context, containerName string, tail int, follow bool, since string) *exec.Cmd {
|
||||||
|
args := []string{"--no-pager", "--output", "cat"}
|
||||||
|
if os.Getuid() != 0 {
|
||||||
|
args = append(args, "--user")
|
||||||
|
}
|
||||||
|
args = append(args, "CONTAINER_NAME="+containerName)
|
||||||
|
if tail > 0 {
|
||||||
|
args = append(args, "--lines", fmt.Sprintf("%d", tail))
|
||||||
|
}
|
||||||
|
if follow {
|
||||||
|
args = append(args, "--follow")
|
||||||
|
}
|
||||||
|
if since != "" {
|
||||||
|
args = append(args, "--since", since)
|
||||||
|
}
|
||||||
|
return exec.CommandContext(ctx, "journalctl", args...) //nolint:gosec // args built programmatically
|
||||||
|
}
|
||||||
|
|
||||||
|
// podmanLogs returns a podman logs command.
|
||||||
|
func (p *Podman) podmanLogs(ctx context.Context, containerName string, tail int, follow, timestamps bool, since string) *exec.Cmd {
|
||||||
|
args := []string{"logs"}
|
||||||
|
if tail > 0 {
|
||||||
|
args = append(args, "--tail", fmt.Sprintf("%d", tail))
|
||||||
|
}
|
||||||
|
if follow {
|
||||||
|
args = append(args, "--follow")
|
||||||
|
}
|
||||||
|
if timestamps {
|
||||||
|
args = append(args, "--timestamps")
|
||||||
|
}
|
||||||
|
if since != "" {
|
||||||
|
args = append(args, "--since", since)
|
||||||
|
}
|
||||||
|
args = append(args, containerName)
|
||||||
|
return exec.CommandContext(ctx, p.command(), args...) //nolint:gosec // args built programmatically
|
||||||
|
}
|
||||||
|
|
||||||
// Login authenticates to a container registry using the given token as
|
// Login authenticates to a container registry using the given token as
|
||||||
// the password. This enables non-interactive push with service account
|
// the password. This enables non-interactive push with service account
|
||||||
// tokens (MCR accepts MCIAS JWTs as passwords).
|
// tokens (MCR accepts MCIAS JWTs as passwords).
|
||||||
|
|||||||
@@ -33,6 +33,15 @@ service McpAgentService {
|
|||||||
|
|
||||||
// Node
|
// Node
|
||||||
rpc NodeStatus(NodeStatusRequest) returns (NodeStatusResponse);
|
rpc NodeStatus(NodeStatusRequest) returns (NodeStatusResponse);
|
||||||
|
|
||||||
|
// DNS (query MCNS)
|
||||||
|
rpc ListDNSRecords(ListDNSRecordsRequest) returns (ListDNSRecordsResponse);
|
||||||
|
|
||||||
|
// Proxy routes (query mc-proxy)
|
||||||
|
rpc ListProxyRoutes(ListProxyRoutesRequest) returns (ListProxyRoutesResponse);
|
||||||
|
|
||||||
|
// Logs
|
||||||
|
rpc Logs(LogsRequest) returns (stream LogsResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Service lifecycle ---
|
// --- Service lifecycle ---
|
||||||
@@ -254,6 +263,7 @@ message NodeStatusResponse {
|
|||||||
uint64 memory_free_bytes = 9;
|
uint64 memory_free_bytes = 9;
|
||||||
double cpu_usage_percent = 10;
|
double cpu_usage_percent = 10;
|
||||||
google.protobuf.Timestamp uptime_since = 11;
|
google.protobuf.Timestamp uptime_since = 11;
|
||||||
|
string agent_version = 12;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Purge ---
|
// --- Purge ---
|
||||||
@@ -282,3 +292,64 @@ message PurgeResult {
|
|||||||
// Why eligible, or why refused.
|
// Why eligible, or why refused.
|
||||||
string reason = 4;
|
string reason = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Logs ---
|
||||||
|
|
||||||
|
message LogsRequest {
|
||||||
|
string service = 1;
|
||||||
|
string component = 2; // optional; defaults to first/only component
|
||||||
|
int32 tail = 3; // number of lines from the end (0 = all)
|
||||||
|
bool follow = 4; // stream new output
|
||||||
|
bool timestamps = 5; // prepend timestamps
|
||||||
|
string since = 6; // show logs since (e.g., "2h", "2026-03-28T00:00:00Z")
|
||||||
|
}
|
||||||
|
|
||||||
|
message LogsResponse {
|
||||||
|
bytes data = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- DNS ---
|
||||||
|
|
||||||
|
message ListDNSRecordsRequest {}
|
||||||
|
|
||||||
|
message DNSZone {
|
||||||
|
string name = 1;
|
||||||
|
repeated DNSRecord records = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message DNSRecord {
|
||||||
|
int64 id = 1;
|
||||||
|
string name = 2;
|
||||||
|
string type = 3;
|
||||||
|
string value = 4;
|
||||||
|
int32 ttl = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ListDNSRecordsResponse {
|
||||||
|
repeated DNSZone zones = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Proxy routes ---
|
||||||
|
|
||||||
|
message ListProxyRoutesRequest {}
|
||||||
|
|
||||||
|
message ProxyRouteInfo {
|
||||||
|
string hostname = 1;
|
||||||
|
string backend = 2;
|
||||||
|
string mode = 3;
|
||||||
|
bool backend_tls = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ProxyListenerInfo {
|
||||||
|
string addr = 1;
|
||||||
|
int32 route_count = 2;
|
||||||
|
int64 active_connections = 3;
|
||||||
|
repeated ProxyRouteInfo routes = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ListProxyRoutesResponse {
|
||||||
|
string version = 1;
|
||||||
|
int64 total_connections = 2;
|
||||||
|
google.protobuf.Timestamp started_at = 3;
|
||||||
|
repeated ProxyListenerInfo listeners = 4;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user