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 }