P2.1 + P3.1: Agent skeleton and CLI skeleton
Agent (P2.1): Agent struct with registry DB, runtime, and logger. gRPC server with TLS 1.3 and MCIAS auth interceptor. Graceful shutdown on SIGINT/SIGTERM. All RPCs return Unimplemented until handlers are built in P2.2-P2.9. CLI (P3.1): Full command tree with all 15 subcommands as stubs (login, deploy, stop, start, restart, list, ps, status, sync, adopt, service show/edit/export, push, pull, node list/add/remove). gRPC dial helper with TLS, CA cert, and bearer token attachment. Both gates for parallel Phase 2+3 work are now open. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
119
internal/agent/agent.go
Normal file
119
internal/agent/agent.go
Normal file
@@ -0,0 +1,119 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
mcpv1 "git.wntrmute.dev/kyle/mcp/gen/mcp/v1"
|
||||
"git.wntrmute.dev/kyle/mcp/internal/auth"
|
||||
"git.wntrmute.dev/kyle/mcp/internal/config"
|
||||
"git.wntrmute.dev/kyle/mcp/internal/registry"
|
||||
"git.wntrmute.dev/kyle/mcp/internal/runtime"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
)
|
||||
|
||||
// Agent is the MCP node agent. It manages containers, stores the registry,
|
||||
// monitors for drift, and serves the gRPC API.
|
||||
type Agent struct {
|
||||
mcpv1.UnimplementedMcpAgentServiceServer
|
||||
|
||||
Config *config.AgentConfig
|
||||
DB *sql.DB
|
||||
Runtime runtime.Runtime
|
||||
Logger *slog.Logger
|
||||
}
|
||||
|
||||
// Run starts the agent: opens the database, sets up the gRPC server with
|
||||
// TLS and auth, and blocks until SIGINT/SIGTERM.
|
||||
func Run(cfg *config.AgentConfig) error {
|
||||
logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
|
||||
Level: parseLogLevel(cfg.Log.Level),
|
||||
}))
|
||||
|
||||
db, err := registry.Open(cfg.Database.Path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("open registry: %w", err)
|
||||
}
|
||||
defer func() { _ = db.Close() }()
|
||||
|
||||
rt := &runtime.Podman{}
|
||||
|
||||
a := &Agent{
|
||||
Config: cfg,
|
||||
DB: db,
|
||||
Runtime: rt,
|
||||
Logger: logger,
|
||||
}
|
||||
|
||||
tlsCert, err := tls.LoadX509KeyPair(cfg.Server.TLSCert, cfg.Server.TLSKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("load TLS cert: %w", err)
|
||||
}
|
||||
tlsConfig := &tls.Config{
|
||||
Certificates: []tls.Certificate{tlsCert},
|
||||
MinVersion: tls.VersionTLS13,
|
||||
}
|
||||
|
||||
validator, err := auth.NewMCIASValidator(cfg.MCIAS.ServerURL, cfg.MCIAS.CACert)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create MCIAS validator: %w", err)
|
||||
}
|
||||
|
||||
server := grpc.NewServer(
|
||||
grpc.Creds(credentials.NewTLS(tlsConfig)),
|
||||
grpc.ChainUnaryInterceptor(
|
||||
auth.AuthInterceptor(validator),
|
||||
),
|
||||
)
|
||||
mcpv1.RegisterMcpAgentServiceServer(server, a)
|
||||
|
||||
lis, err := net.Listen("tcp", cfg.Server.GRPCAddr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("listen %q: %w", cfg.Server.GRPCAddr, err)
|
||||
}
|
||||
|
||||
logger.Info("agent starting",
|
||||
"addr", cfg.Server.GRPCAddr,
|
||||
"node", cfg.Agent.NodeName,
|
||||
"runtime", cfg.Agent.ContainerRuntime,
|
||||
)
|
||||
|
||||
// Graceful shutdown on signal.
|
||||
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
|
||||
defer stop()
|
||||
|
||||
errCh := make(chan error, 1)
|
||||
go func() {
|
||||
errCh <- server.Serve(lis)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
logger.Info("shutting down")
|
||||
server.GracefulStop()
|
||||
return nil
|
||||
case err := <-errCh:
|
||||
return fmt.Errorf("serve: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
func parseLogLevel(level string) slog.Level {
|
||||
switch level {
|
||||
case "debug":
|
||||
return slog.LevelDebug
|
||||
case "warn":
|
||||
return slog.LevelWarn
|
||||
case "error":
|
||||
return slog.LevelError
|
||||
default:
|
||||
return slog.LevelInfo
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user