MasterConfig with TOML loading, env overrides (MCP_MASTER_*), defaults, and validation. Follows the exact pattern of AgentConfig. Includes: server, database, MCIAS, edge (allowed_domains), registration (allowed_agents, max_nodes), timeouts, MCNS, bootstrap [[nodes]], and master service token path. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
169 lines
4.9 KiB
Go
169 lines
4.9 KiB
Go
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"time"
|
|
|
|
toml "github.com/pelletier/go-toml/v2"
|
|
)
|
|
|
|
// MasterConfig is the configuration for the mcp-master daemon.
|
|
type MasterConfig struct {
|
|
Server ServerConfig `toml:"server"`
|
|
Database DatabaseConfig `toml:"database"`
|
|
MCIAS MCIASConfig `toml:"mcias"`
|
|
Edge EdgeConfig `toml:"edge"`
|
|
Registration RegistrationConfig `toml:"registration"`
|
|
Timeouts TimeoutsConfig `toml:"timeouts"`
|
|
MCNS MCNSConfig `toml:"mcns"`
|
|
Log LogConfig `toml:"log"`
|
|
Nodes []MasterNodeConfig `toml:"nodes"`
|
|
|
|
// Master holds the master's own MCIAS service token for dialing agents.
|
|
Master MasterSettings `toml:"master"`
|
|
}
|
|
|
|
// MasterSettings holds settings specific to the master's own identity.
|
|
type MasterSettings struct {
|
|
// ServiceTokenPath is the path to the MCIAS service token file
|
|
// used by the master to authenticate to agents.
|
|
ServiceTokenPath string `toml:"service_token_path"`
|
|
|
|
// CACert is the path to the CA certificate for verifying agent TLS.
|
|
CACert string `toml:"ca_cert"`
|
|
}
|
|
|
|
// EdgeConfig holds settings for edge route management.
|
|
type EdgeConfig struct {
|
|
// AllowedDomains is the list of domains that public hostnames
|
|
// must fall under. Validation uses proper domain label matching.
|
|
AllowedDomains []string `toml:"allowed_domains"`
|
|
}
|
|
|
|
// RegistrationConfig holds agent registration settings.
|
|
type RegistrationConfig struct {
|
|
// AllowedAgents is the list of MCIAS service identities permitted
|
|
// to register with the master (e.g., "agent-rift", "agent-svc").
|
|
AllowedAgents []string `toml:"allowed_agents"`
|
|
|
|
// MaxNodes is the maximum number of registered nodes.
|
|
MaxNodes int `toml:"max_nodes"`
|
|
}
|
|
|
|
// TimeoutsConfig holds timeout durations for master operations.
|
|
type TimeoutsConfig struct {
|
|
Deploy Duration `toml:"deploy"`
|
|
EdgeRoute Duration `toml:"edge_route"`
|
|
HealthCheck Duration `toml:"health_check"`
|
|
Undeploy Duration `toml:"undeploy"`
|
|
Snapshot Duration `toml:"snapshot"`
|
|
}
|
|
|
|
// MasterNodeConfig is a bootstrap node entry in the master config.
|
|
type MasterNodeConfig struct {
|
|
Name string `toml:"name"`
|
|
Address string `toml:"address"`
|
|
Role string `toml:"role"` // "worker", "edge", or "master"
|
|
}
|
|
|
|
// LoadMasterConfig reads and validates a master configuration file.
|
|
func LoadMasterConfig(path string) (*MasterConfig, error) {
|
|
data, err := os.ReadFile(path) //nolint:gosec // config path from trusted CLI flag
|
|
if err != nil {
|
|
return nil, fmt.Errorf("read config %q: %w", path, err)
|
|
}
|
|
|
|
var cfg MasterConfig
|
|
if err := toml.Unmarshal(data, &cfg); err != nil {
|
|
return nil, fmt.Errorf("parse config %q: %w", path, err)
|
|
}
|
|
|
|
applyMasterDefaults(&cfg)
|
|
applyMasterEnvOverrides(&cfg)
|
|
|
|
if err := validateMasterConfig(&cfg); err != nil {
|
|
return nil, fmt.Errorf("validate config: %w", err)
|
|
}
|
|
|
|
return &cfg, nil
|
|
}
|
|
|
|
func applyMasterDefaults(cfg *MasterConfig) {
|
|
if cfg.Log.Level == "" {
|
|
cfg.Log.Level = "info"
|
|
}
|
|
if cfg.Registration.MaxNodes == 0 {
|
|
cfg.Registration.MaxNodes = 16
|
|
}
|
|
if cfg.Timeouts.Deploy.Duration == 0 {
|
|
cfg.Timeouts.Deploy.Duration = 5 * time.Minute
|
|
}
|
|
if cfg.Timeouts.EdgeRoute.Duration == 0 {
|
|
cfg.Timeouts.EdgeRoute.Duration = 30 * time.Second
|
|
}
|
|
if cfg.Timeouts.HealthCheck.Duration == 0 {
|
|
cfg.Timeouts.HealthCheck.Duration = 5 * time.Second
|
|
}
|
|
if cfg.Timeouts.Undeploy.Duration == 0 {
|
|
cfg.Timeouts.Undeploy.Duration = 2 * time.Minute
|
|
}
|
|
if cfg.Timeouts.Snapshot.Duration == 0 {
|
|
cfg.Timeouts.Snapshot.Duration = 10 * time.Minute
|
|
}
|
|
if cfg.MCNS.Zone == "" {
|
|
cfg.MCNS.Zone = "svc.mcp.metacircular.net"
|
|
}
|
|
for i := range cfg.Nodes {
|
|
if cfg.Nodes[i].Role == "" {
|
|
cfg.Nodes[i].Role = "worker"
|
|
}
|
|
}
|
|
}
|
|
|
|
func applyMasterEnvOverrides(cfg *MasterConfig) {
|
|
if v := os.Getenv("MCP_MASTER_SERVER_GRPC_ADDR"); v != "" {
|
|
cfg.Server.GRPCAddr = v
|
|
}
|
|
if v := os.Getenv("MCP_MASTER_SERVER_TLS_CERT"); v != "" {
|
|
cfg.Server.TLSCert = v
|
|
}
|
|
if v := os.Getenv("MCP_MASTER_SERVER_TLS_KEY"); v != "" {
|
|
cfg.Server.TLSKey = v
|
|
}
|
|
if v := os.Getenv("MCP_MASTER_DATABASE_PATH"); v != "" {
|
|
cfg.Database.Path = v
|
|
}
|
|
if v := os.Getenv("MCP_MASTER_LOG_LEVEL"); v != "" {
|
|
cfg.Log.Level = v
|
|
}
|
|
}
|
|
|
|
func validateMasterConfig(cfg *MasterConfig) error {
|
|
if cfg.Server.GRPCAddr == "" {
|
|
return fmt.Errorf("server.grpc_addr is required")
|
|
}
|
|
if cfg.Server.TLSCert == "" {
|
|
return fmt.Errorf("server.tls_cert is required")
|
|
}
|
|
if cfg.Server.TLSKey == "" {
|
|
return fmt.Errorf("server.tls_key is required")
|
|
}
|
|
if cfg.Database.Path == "" {
|
|
return fmt.Errorf("database.path is required")
|
|
}
|
|
if cfg.MCIAS.ServerURL == "" {
|
|
return fmt.Errorf("mcias.server_url is required")
|
|
}
|
|
if cfg.MCIAS.ServiceName == "" {
|
|
return fmt.Errorf("mcias.service_name is required")
|
|
}
|
|
if len(cfg.Nodes) == 0 {
|
|
return fmt.Errorf("at least one [[nodes]] entry is required")
|
|
}
|
|
if cfg.Master.ServiceTokenPath == "" {
|
|
return fmt.Errorf("master.service_token_path is required")
|
|
}
|
|
return nil
|
|
}
|