Major Improvements: - Added retry deployment buttons in machine list for failed deployments - Added retry button in SSH console modal footer for enhanced UX - Enhanced deployment process with comprehensive cleanup of existing services - Improved binary installation with password-based sudo authentication - Updated configuration generation to include all required sections (agent, ai, network, security) - Fixed deployment verification and error handling Security Enhancements: - Enhanced verifiedStopExistingServices with thorough cleanup process - Improved binary copying with proper sudo authentication - Added comprehensive configuration validation UX Improvements: - Users can retry deployments without re-running machine discovery - Retry buttons available from both machine list and console modal - Real-time deployment progress with detailed console output - Clear error states with actionable retry options Technical Changes: - Modified ServiceDeployment.tsx with retry button components - Enhanced api/setup_manager.go with improved deployment functions - Updated main.go with command line argument support (--config, --setup) - Added comprehensive zero-trust security validation system 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
817 lines
26 KiB
Go
817 lines
26 KiB
Go
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"chorus.services/bzzz/pkg/security"
|
|
"gopkg.in/yaml.v2"
|
|
)
|
|
|
|
// SecurityConfig holds cluster security and election configuration
|
|
type SecurityConfig struct {
|
|
// Admin key sharing
|
|
AdminKeyShares ShamirShare `yaml:"admin_key_shares" json:"admin_key_shares"`
|
|
ElectionConfig ElectionConfig `yaml:"election_config" json:"election_config"`
|
|
|
|
// Key management
|
|
KeyRotationDays int `yaml:"key_rotation_days,omitempty" json:"key_rotation_days,omitempty"`
|
|
AuditLogging bool `yaml:"audit_logging" json:"audit_logging"`
|
|
AuditPath string `yaml:"audit_path,omitempty" json:"audit_path,omitempty"`
|
|
}
|
|
|
|
// AIConfig holds AI/LLM integration settings
|
|
type AIConfig struct {
|
|
Ollama OllamaConfig `yaml:"ollama"`
|
|
OpenAI OpenAIConfig `yaml:"openai"`
|
|
}
|
|
|
|
// OllamaConfig holds Ollama API configuration
|
|
type OllamaConfig struct {
|
|
Endpoint string `yaml:"endpoint"`
|
|
Timeout time.Duration `yaml:"timeout"`
|
|
Models []string `yaml:"models"`
|
|
}
|
|
|
|
// OpenAIConfig holds OpenAI API configuration
|
|
type OpenAIConfig struct {
|
|
APIKey string `yaml:"api_key"`
|
|
Endpoint string `yaml:"endpoint"`
|
|
Timeout time.Duration `yaml:"timeout"`
|
|
}
|
|
|
|
// Config represents the complete configuration for a Bzzz agent
|
|
type Config struct {
|
|
WHOOSHAPI WHOOSHAPIConfig `yaml:"hive_api"`
|
|
Agent AgentConfig `yaml:"agent"`
|
|
GitHub GitHubConfig `yaml:"github"`
|
|
P2P P2PConfig `yaml:"p2p"`
|
|
Logging LoggingConfig `yaml:"logging"`
|
|
HCFS HCFSConfig `yaml:"hcfs"`
|
|
Slurp SlurpConfig `yaml:"slurp"`
|
|
V2 V2Config `yaml:"v2"` // BZZZ v2 protocol settings
|
|
UCXL UCXLConfig `yaml:"ucxl"` // UCXL protocol settings
|
|
Security SecurityConfig `yaml:"security"` // Cluster security and elections
|
|
AI AIConfig `yaml:"ai"` // AI/LLM integration settings
|
|
}
|
|
|
|
// WHOOSHAPIConfig holds WHOOSH system integration settings
|
|
type WHOOSHAPIConfig struct {
|
|
BaseURL string `yaml:"base_url"`
|
|
APIKey string `yaml:"api_key"`
|
|
Timeout time.Duration `yaml:"timeout"`
|
|
RetryCount int `yaml:"retry_count"`
|
|
}
|
|
|
|
// CollaborationConfig holds role-based collaboration settings
|
|
type CollaborationConfig struct {
|
|
PreferredMessageTypes []string `yaml:"preferred_message_types"`
|
|
AutoSubscribeToRoles []string `yaml:"auto_subscribe_to_roles"`
|
|
AutoSubscribeToExpertise []string `yaml:"auto_subscribe_to_expertise"`
|
|
ResponseTimeoutSeconds int `yaml:"response_timeout_seconds"`
|
|
MaxCollaborationDepth int `yaml:"max_collaboration_depth"`
|
|
EscalationThreshold int `yaml:"escalation_threshold"`
|
|
CustomTopicSubscriptions []string `yaml:"custom_topic_subscriptions"`
|
|
}
|
|
|
|
// AgentConfig holds agent-specific configuration
|
|
type AgentConfig struct {
|
|
ID string `yaml:"id"`
|
|
Capabilities []string `yaml:"capabilities"`
|
|
PollInterval time.Duration `yaml:"poll_interval"`
|
|
MaxTasks int `yaml:"max_tasks"`
|
|
Models []string `yaml:"models"`
|
|
Specialization string `yaml:"specialization"`
|
|
ModelSelectionWebhook string `yaml:"model_selection_webhook"`
|
|
DefaultReasoningModel string `yaml:"default_reasoning_model"`
|
|
SandboxImage string `yaml:"sandbox_image"`
|
|
|
|
// Role-based configuration from Bees-AgenticWorkers
|
|
Role string `yaml:"role"`
|
|
SystemPrompt string `yaml:"system_prompt"`
|
|
ReportsTo []string `yaml:"reports_to"`
|
|
Expertise []string `yaml:"expertise"`
|
|
Deliverables []string `yaml:"deliverables"`
|
|
|
|
// Role-based collaboration settings
|
|
CollaborationSettings CollaborationConfig `yaml:"collaboration"`
|
|
}
|
|
|
|
// GitHubConfig holds GitHub integration settings
|
|
type GitHubConfig struct {
|
|
TokenFile string `yaml:"token_file"`
|
|
UserAgent string `yaml:"user_agent"`
|
|
Timeout time.Duration `yaml:"timeout"`
|
|
RateLimit bool `yaml:"rate_limit"`
|
|
Assignee string `yaml:"assignee"`
|
|
}
|
|
|
|
// P2PConfig holds P2P networking configuration
|
|
type P2PConfig struct {
|
|
ServiceTag string `yaml:"service_tag"`
|
|
BzzzTopic string `yaml:"bzzz_topic"`
|
|
HmmmTopic string `yaml:"hmmm_topic"`
|
|
DiscoveryTimeout time.Duration `yaml:"discovery_timeout"`
|
|
|
|
// Human escalation settings
|
|
EscalationWebhook string `yaml:"escalation_webhook"`
|
|
EscalationKeywords []string `yaml:"escalation_keywords"`
|
|
ConversationLimit int `yaml:"conversation_limit"`
|
|
}
|
|
|
|
// LoggingConfig holds logging configuration
|
|
type LoggingConfig struct {
|
|
Level string `yaml:"level"`
|
|
Format string `yaml:"format"`
|
|
Output string `yaml:"output"`
|
|
Structured bool `yaml:"structured"`
|
|
}
|
|
|
|
// V2Config holds BZZZ v2 protocol configuration
|
|
type V2Config struct {
|
|
// Enable v2 protocol features
|
|
Enabled bool `yaml:"enabled" json:"enabled"`
|
|
|
|
// Protocol version
|
|
ProtocolVersion string `yaml:"protocol_version" json:"protocol_version"`
|
|
|
|
// URI resolution settings
|
|
URIResolution URIResolutionConfig `yaml:"uri_resolution" json:"uri_resolution"`
|
|
|
|
// DHT settings
|
|
DHT DHTConfig `yaml:"dht" json:"dht"`
|
|
|
|
// Semantic addressing
|
|
SemanticAddressing SemanticAddressingConfig `yaml:"semantic_addressing" json:"semantic_addressing"`
|
|
|
|
// Feature flags
|
|
FeatureFlags map[string]bool `yaml:"feature_flags" json:"feature_flags"`
|
|
}
|
|
|
|
// URIResolutionConfig holds URI resolution settings
|
|
type URIResolutionConfig struct {
|
|
CacheTTL time.Duration `yaml:"cache_ttl" json:"cache_ttl"`
|
|
MaxPeersPerResult int `yaml:"max_peers_per_result" json:"max_peers_per_result"`
|
|
DefaultStrategy string `yaml:"default_strategy" json:"default_strategy"`
|
|
ResolutionTimeout time.Duration `yaml:"resolution_timeout" json:"resolution_timeout"`
|
|
}
|
|
|
|
// DHTConfig holds DHT-specific configuration
|
|
type DHTConfig struct {
|
|
Enabled bool `yaml:"enabled" json:"enabled"`
|
|
BootstrapPeers []string `yaml:"bootstrap_peers" json:"bootstrap_peers"`
|
|
Mode string `yaml:"mode" json:"mode"` // "client", "server", "auto"
|
|
ProtocolPrefix string `yaml:"protocol_prefix" json:"protocol_prefix"`
|
|
BootstrapTimeout time.Duration `yaml:"bootstrap_timeout" json:"bootstrap_timeout"`
|
|
DiscoveryInterval time.Duration `yaml:"discovery_interval" json:"discovery_interval"`
|
|
AutoBootstrap bool `yaml:"auto_bootstrap" json:"auto_bootstrap"`
|
|
}
|
|
|
|
// SemanticAddressingConfig holds semantic addressing settings
|
|
type SemanticAddressingConfig struct {
|
|
EnableWildcards bool `yaml:"enable_wildcards" json:"enable_wildcards"`
|
|
DefaultAgent string `yaml:"default_agent" json:"default_agent"`
|
|
DefaultRole string `yaml:"default_role" json:"default_role"`
|
|
DefaultProject string `yaml:"default_project" json:"default_project"`
|
|
EnableRoleHierarchy bool `yaml:"enable_role_hierarchy" json:"enable_role_hierarchy"`
|
|
}
|
|
|
|
// UCXLConfig holds UCXL protocol configuration
|
|
type UCXLConfig struct {
|
|
// Enable UCXL protocol
|
|
Enabled bool `yaml:"enabled" json:"enabled"`
|
|
|
|
// UCXI server configuration
|
|
Server UCXIServerConfig `yaml:"server" json:"server"`
|
|
|
|
// Address resolution settings
|
|
Resolution UCXLResolutionConfig `yaml:"resolution" json:"resolution"`
|
|
|
|
// Storage settings
|
|
Storage UCXLStorageConfig `yaml:"storage" json:"storage"`
|
|
|
|
// P2P integration settings
|
|
P2PIntegration UCXLP2PConfig `yaml:"p2p_integration" json:"p2p_integration"`
|
|
}
|
|
|
|
// UCXIServerConfig holds UCXI server settings
|
|
type UCXIServerConfig struct {
|
|
Port int `yaml:"port" json:"port"`
|
|
BasePath string `yaml:"base_path" json:"base_path"`
|
|
Enabled bool `yaml:"enabled" json:"enabled"`
|
|
}
|
|
|
|
// UCXLResolutionConfig holds address resolution settings
|
|
type UCXLResolutionConfig struct {
|
|
CacheTTL time.Duration `yaml:"cache_ttl" json:"cache_ttl"`
|
|
EnableWildcards bool `yaml:"enable_wildcards" json:"enable_wildcards"`
|
|
MaxResults int `yaml:"max_results" json:"max_results"`
|
|
}
|
|
|
|
// UCXLStorageConfig holds storage settings
|
|
type UCXLStorageConfig struct {
|
|
Type string `yaml:"type" json:"type"` // "filesystem", "memory"
|
|
Directory string `yaml:"directory" json:"directory"`
|
|
MaxSize int64 `yaml:"max_size" json:"max_size"` // in bytes
|
|
}
|
|
|
|
// UCXLP2PConfig holds P2P integration settings
|
|
type UCXLP2PConfig struct {
|
|
EnableAnnouncement bool `yaml:"enable_announcement" json:"enable_announcement"`
|
|
EnableDiscovery bool `yaml:"enable_discovery" json:"enable_discovery"`
|
|
AnnouncementTopic string `yaml:"announcement_topic" json:"announcement_topic"`
|
|
DiscoveryTimeout time.Duration `yaml:"discovery_timeout" json:"discovery_timeout"`
|
|
}
|
|
|
|
// HCFSConfig holds HCFS integration configuration
|
|
type HCFSConfig struct {
|
|
// API settings
|
|
APIURL string `yaml:"api_url" json:"api_url"`
|
|
APITimeout time.Duration `yaml:"api_timeout" json:"api_timeout"`
|
|
|
|
// Workspace settings
|
|
MountPath string `yaml:"mount_path" json:"mount_path"`
|
|
WorkspaceTimeout time.Duration `yaml:"workspace_timeout" json:"workspace_timeout"`
|
|
|
|
// FUSE settings
|
|
FUSEEnabled bool `yaml:"fuse_enabled" json:"fuse_enabled"`
|
|
FUSEMountPoint string `yaml:"fuse_mount_point" json:"fuse_mount_point"`
|
|
|
|
// Cleanup settings
|
|
IdleCleanupInterval time.Duration `yaml:"idle_cleanup_interval" json:"idle_cleanup_interval"`
|
|
MaxIdleTime time.Duration `yaml:"max_idle_time" json:"max_idle_time"`
|
|
|
|
// Storage settings
|
|
StoreArtifacts bool `yaml:"store_artifacts" json:"store_artifacts"`
|
|
CompressArtifacts bool `yaml:"compress_artifacts" json:"compress_artifacts"`
|
|
|
|
// Enable/disable HCFS integration
|
|
Enabled bool `yaml:"enabled" json:"enabled"`
|
|
}
|
|
|
|
// LoadConfig loads configuration from file, environment variables, and defaults
|
|
func LoadConfig(configPath string) (*Config, error) {
|
|
// Start with defaults
|
|
config := getDefaultConfig()
|
|
|
|
// Load from file if it exists
|
|
if configPath != "" && fileExists(configPath) {
|
|
if err := loadFromFile(config, configPath); err != nil {
|
|
return nil, fmt.Errorf("failed to load config file: %w", err)
|
|
}
|
|
}
|
|
|
|
// Override with environment variables
|
|
if err := loadFromEnv(config); err != nil {
|
|
return nil, fmt.Errorf("failed to load environment variables: %w", err)
|
|
}
|
|
|
|
// Validate configuration
|
|
if err := validateConfig(config); err != nil {
|
|
return nil, fmt.Errorf("invalid configuration: %w", err)
|
|
}
|
|
|
|
return config, nil
|
|
}
|
|
|
|
// getDefaultConfig returns the default configuration
|
|
func getDefaultConfig() *Config {
|
|
return &Config{
|
|
WHOOSHAPI: WHOOSHAPIConfig{
|
|
BaseURL: "https://hive.home.deepblack.cloud",
|
|
Timeout: 30 * time.Second,
|
|
RetryCount: 3,
|
|
},
|
|
Agent: AgentConfig{
|
|
Capabilities: []string{"general", "reasoning", "task-coordination"},
|
|
PollInterval: 30 * time.Second,
|
|
MaxTasks: 3,
|
|
Models: []string{"phi3", "llama3.1"},
|
|
Specialization: "general_developer",
|
|
ModelSelectionWebhook: "https://n8n.home.deepblack.cloud/webhook/model-selection",
|
|
DefaultReasoningModel: "phi3",
|
|
SandboxImage: "registry.home.deepblack.cloud/tony/bzzz-sandbox:latest",
|
|
},
|
|
GitHub: GitHubConfig{
|
|
TokenFile: "/home/tony/chorus/business/secrets/gh-token",
|
|
UserAgent: "Bzzz-P2P-Agent/1.0",
|
|
Timeout: 30 * time.Second,
|
|
RateLimit: true,
|
|
Assignee: "anthonyrawlins",
|
|
},
|
|
P2P: P2PConfig{
|
|
ServiceTag: "bzzz-peer-discovery",
|
|
BzzzTopic: "bzzz/coordination/v1",
|
|
HmmmTopic: "hmmm/meta-discussion/v1",
|
|
DiscoveryTimeout: 10 * time.Second,
|
|
EscalationWebhook: "https://n8n.home.deepblack.cloud/webhook-test/human-escalation",
|
|
EscalationKeywords: []string{"stuck", "help", "human", "escalate", "clarification needed", "manual intervention"},
|
|
ConversationLimit: 10,
|
|
},
|
|
Logging: LoggingConfig{
|
|
Level: "info",
|
|
Format: "text",
|
|
Output: "stdout",
|
|
Structured: false,
|
|
},
|
|
HCFS: HCFSConfig{
|
|
APIURL: "http://localhost:8000",
|
|
APITimeout: 30 * time.Second,
|
|
MountPath: "/tmp/hcfs-workspaces",
|
|
WorkspaceTimeout: 2 * time.Hour,
|
|
FUSEEnabled: false,
|
|
FUSEMountPoint: "/mnt/hcfs",
|
|
IdleCleanupInterval: 15 * time.Minute,
|
|
MaxIdleTime: 1 * time.Hour,
|
|
StoreArtifacts: true,
|
|
CompressArtifacts: false,
|
|
Enabled: true,
|
|
},
|
|
Slurp: GetDefaultSlurpConfig(),
|
|
UCXL: UCXLConfig{
|
|
Enabled: false, // Disabled by default
|
|
Server: UCXIServerConfig{
|
|
Port: 8081,
|
|
BasePath: "/bzzz",
|
|
Enabled: true,
|
|
},
|
|
Resolution: UCXLResolutionConfig{
|
|
CacheTTL: 5 * time.Minute,
|
|
EnableWildcards: true,
|
|
MaxResults: 50,
|
|
},
|
|
Storage: UCXLStorageConfig{
|
|
Type: "filesystem",
|
|
Directory: "/tmp/bzzz-ucxl-storage",
|
|
MaxSize: 100 * 1024 * 1024, // 100MB
|
|
},
|
|
P2PIntegration: UCXLP2PConfig{
|
|
EnableAnnouncement: true,
|
|
EnableDiscovery: true,
|
|
AnnouncementTopic: "bzzz/ucxl/announcement/v1",
|
|
DiscoveryTimeout: 30 * time.Second,
|
|
},
|
|
},
|
|
Security: SecurityConfig{
|
|
AdminKeyShares: ShamirShare{
|
|
Threshold: 3,
|
|
TotalShares: 5,
|
|
},
|
|
ElectionConfig: ElectionConfig{
|
|
HeartbeatTimeout: 5 * time.Second,
|
|
DiscoveryTimeout: 30 * time.Second,
|
|
ElectionTimeout: 15 * time.Second,
|
|
MaxDiscoveryAttempts: 6,
|
|
DiscoveryBackoff: 5 * time.Second,
|
|
MinimumQuorum: 3,
|
|
ConsensusAlgorithm: "raft",
|
|
SplitBrainDetection: true,
|
|
ConflictResolution: "highest_uptime",
|
|
},
|
|
KeyRotationDays: 90,
|
|
AuditLogging: true,
|
|
AuditPath: ".bzzz/security-audit.log",
|
|
},
|
|
V2: V2Config{
|
|
Enabled: false, // Disabled by default for backward compatibility
|
|
ProtocolVersion: "2.0.0",
|
|
URIResolution: URIResolutionConfig{
|
|
CacheTTL: 5 * time.Minute,
|
|
MaxPeersPerResult: 5,
|
|
DefaultStrategy: "best_match",
|
|
ResolutionTimeout: 30 * time.Second,
|
|
},
|
|
DHT: DHTConfig{
|
|
Enabled: false, // Disabled by default
|
|
BootstrapPeers: []string{},
|
|
Mode: "auto",
|
|
ProtocolPrefix: "/bzzz",
|
|
BootstrapTimeout: 30 * time.Second,
|
|
DiscoveryInterval: 60 * time.Second,
|
|
AutoBootstrap: false,
|
|
},
|
|
SemanticAddressing: SemanticAddressingConfig{
|
|
EnableWildcards: true,
|
|
DefaultAgent: "any",
|
|
DefaultRole: "any",
|
|
DefaultProject: "any",
|
|
EnableRoleHierarchy: true,
|
|
},
|
|
FeatureFlags: map[string]bool{
|
|
"uri_protocol": false,
|
|
"semantic_addressing": false,
|
|
"dht_discovery": false,
|
|
"advanced_resolution": false,
|
|
},
|
|
},
|
|
AI: AIConfig{
|
|
Ollama: OllamaConfig{
|
|
Endpoint: "http://localhost:11434",
|
|
Timeout: 30 * time.Second,
|
|
Models: []string{"phi3", "llama3.1"},
|
|
},
|
|
OpenAI: OpenAIConfig{
|
|
APIKey: "",
|
|
Endpoint: "https://api.openai.com/v1",
|
|
Timeout: 30 * time.Second,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
// loadFromFile loads configuration from a YAML file with zero-trust validation
|
|
func loadFromFile(config *Config, filePath string) error {
|
|
// SECURITY: Validate file path to prevent directory traversal
|
|
validator := security.NewSecurityValidator()
|
|
if err := validator.ValidateFilePath(filePath); err != nil {
|
|
return fmt.Errorf("security validation failed for config path: %w", err)
|
|
}
|
|
|
|
data, err := ioutil.ReadFile(filePath)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read config file: %w", err)
|
|
}
|
|
|
|
// SECURITY: Limit file size to prevent memory exhaustion attacks
|
|
maxConfigSize := 1024 * 1024 // 1MB limit for config files
|
|
if len(data) > maxConfigSize {
|
|
return fmt.Errorf("config file too large: %d bytes exceeds limit of %d bytes", len(data), maxConfigSize)
|
|
}
|
|
|
|
if err := yaml.Unmarshal(data, config); err != nil {
|
|
return fmt.Errorf("failed to parse YAML config: %w", err)
|
|
}
|
|
|
|
// SECURITY: Validate all configuration values after loading
|
|
if err := validateConfigSecurity(config, validator); err != nil {
|
|
return fmt.Errorf("security validation failed for loaded config: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// loadFromEnv loads configuration from environment variables
|
|
func loadFromEnv(config *Config) error {
|
|
// WHOOSH API configuration
|
|
if url := os.Getenv("BZZZ_HIVE_API_URL"); url != "" {
|
|
config.WHOOSHAPI.BaseURL = url
|
|
}
|
|
if apiKey := os.Getenv("BZZZ_HIVE_API_KEY"); apiKey != "" {
|
|
config.WHOOSHAPI.APIKey = apiKey
|
|
}
|
|
|
|
// Agent configuration
|
|
if agentID := os.Getenv("BZZZ_AGENT_ID"); agentID != "" {
|
|
config.Agent.ID = agentID
|
|
}
|
|
if capabilities := os.Getenv("BZZZ_AGENT_CAPABILITIES"); capabilities != "" {
|
|
config.Agent.Capabilities = strings.Split(capabilities, ",")
|
|
}
|
|
if specialization := os.Getenv("BZZZ_AGENT_SPECIALIZATION"); specialization != "" {
|
|
config.Agent.Specialization = specialization
|
|
}
|
|
if modelWebhook := os.Getenv("BZZZ_MODEL_SELECTION_WEBHOOK"); modelWebhook != "" {
|
|
config.Agent.ModelSelectionWebhook = modelWebhook
|
|
}
|
|
|
|
// GitHub configuration
|
|
if tokenFile := os.Getenv("BZZZ_GITHUB_TOKEN_FILE"); tokenFile != "" {
|
|
config.GitHub.TokenFile = tokenFile
|
|
}
|
|
|
|
// P2P configuration
|
|
if webhook := os.Getenv("BZZZ_ESCALATION_WEBHOOK"); webhook != "" {
|
|
config.P2P.EscalationWebhook = webhook
|
|
}
|
|
|
|
// Logging configuration
|
|
if level := os.Getenv("BZZZ_LOG_LEVEL"); level != "" {
|
|
config.Logging.Level = level
|
|
}
|
|
|
|
// SLURP configuration
|
|
if slurpURL := os.Getenv("BZZZ_SLURP_URL"); slurpURL != "" {
|
|
config.Slurp.BaseURL = slurpURL
|
|
}
|
|
if slurpKey := os.Getenv("BZZZ_SLURP_API_KEY"); slurpKey != "" {
|
|
config.Slurp.APIKey = slurpKey
|
|
}
|
|
if slurpEnabled := os.Getenv("BZZZ_SLURP_ENABLED"); slurpEnabled == "true" {
|
|
config.Slurp.Enabled = true
|
|
}
|
|
|
|
// UCXL protocol configuration
|
|
if ucxlEnabled := os.Getenv("BZZZ_UCXL_ENABLED"); ucxlEnabled == "true" {
|
|
config.UCXL.Enabled = true
|
|
}
|
|
if ucxiPort := os.Getenv("BZZZ_UCXI_PORT"); ucxiPort != "" {
|
|
// Would need strconv.Atoi but keeping simple for now
|
|
// In production, add proper integer parsing
|
|
}
|
|
|
|
// V2 protocol configuration
|
|
if v2Enabled := os.Getenv("BZZZ_V2_ENABLED"); v2Enabled == "true" {
|
|
config.V2.Enabled = true
|
|
}
|
|
if dhtEnabled := os.Getenv("BZZZ_DHT_ENABLED"); dhtEnabled == "true" {
|
|
config.V2.DHT.Enabled = true
|
|
}
|
|
if dhtMode := os.Getenv("BZZZ_DHT_MODE"); dhtMode != "" {
|
|
config.V2.DHT.Mode = dhtMode
|
|
}
|
|
if bootstrapPeers := os.Getenv("BZZZ_DHT_BOOTSTRAP_PEERS"); bootstrapPeers != "" {
|
|
config.V2.DHT.BootstrapPeers = strings.Split(bootstrapPeers, ",")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// validateConfig validates the configuration values
|
|
func validateConfig(config *Config) error {
|
|
// Validate required fields
|
|
if config.WHOOSHAPI.BaseURL == "" {
|
|
return fmt.Errorf("hive_api.base_url is required")
|
|
}
|
|
|
|
// Note: Agent.ID can be empty - it will be auto-generated from node ID in main.go
|
|
|
|
if len(config.Agent.Capabilities) == 0 {
|
|
return fmt.Errorf("agent.capabilities cannot be empty")
|
|
}
|
|
|
|
if config.Agent.PollInterval <= 0 {
|
|
return fmt.Errorf("agent.poll_interval must be positive")
|
|
}
|
|
|
|
if config.Agent.MaxTasks <= 0 {
|
|
return fmt.Errorf("agent.max_tasks must be positive")
|
|
}
|
|
|
|
// Validate GitHub token file exists if specified
|
|
if config.GitHub.TokenFile != "" && !fileExists(config.GitHub.TokenFile) {
|
|
return fmt.Errorf("github token file does not exist: %s", config.GitHub.TokenFile)
|
|
}
|
|
|
|
// Validate SLURP configuration
|
|
if err := ValidateSlurpConfig(config.Slurp); err != nil {
|
|
return fmt.Errorf("slurp configuration invalid: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// SaveConfig saves the configuration to a YAML file
|
|
func SaveConfig(config *Config, filePath string) error {
|
|
data, err := yaml.Marshal(config)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal config to YAML: %w", err)
|
|
}
|
|
|
|
if err := ioutil.WriteFile(filePath, data, 0644); err != nil {
|
|
return fmt.Errorf("failed to write config file: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetGitHubToken reads the GitHub token from the configured file
|
|
func (c *Config) GetGitHubToken() (string, error) {
|
|
if c.GitHub.TokenFile == "" {
|
|
return "", fmt.Errorf("no GitHub token file configured")
|
|
}
|
|
|
|
tokenBytes, err := ioutil.ReadFile(c.GitHub.TokenFile)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to read GitHub token: %w", err)
|
|
}
|
|
|
|
return strings.TrimSpace(string(tokenBytes)), nil
|
|
}
|
|
|
|
// fileExists checks if a file exists
|
|
func fileExists(filePath string) bool {
|
|
_, err := os.Stat(filePath)
|
|
return err == nil
|
|
}
|
|
|
|
// GenerateDefaultConfigFile creates a default configuration file
|
|
func GenerateDefaultConfigFile(filePath string) error {
|
|
config := getDefaultConfig()
|
|
return SaveConfig(config, filePath)
|
|
}
|
|
|
|
// IsSetupRequired checks if BZZZ needs to be set up (no valid configuration exists)
|
|
func IsSetupRequired(configPath string) bool {
|
|
// Check if config file exists
|
|
if !fileExists(configPath) {
|
|
return true
|
|
}
|
|
|
|
// Try to load the configuration
|
|
_, err := LoadConfig(configPath)
|
|
if err != nil {
|
|
return true
|
|
}
|
|
|
|
// Configuration exists and is valid
|
|
return false
|
|
}
|
|
|
|
// IsValidConfiguration checks if a configuration is valid for production use
|
|
func IsValidConfiguration(config *Config) bool {
|
|
// Check essential configuration elements
|
|
if config.Agent.ID == "" {
|
|
return false
|
|
}
|
|
|
|
if len(config.Agent.Capabilities) == 0 {
|
|
return false
|
|
}
|
|
|
|
// Configuration appears valid
|
|
return true
|
|
}
|
|
|
|
// validateConfigSecurity performs zero-trust validation on all configuration values
|
|
func validateConfigSecurity(config *Config, validator *security.SecurityValidator) error {
|
|
// Validate Agent configuration
|
|
if config.Agent.ID != "" {
|
|
// Agent IDs should be alphanumeric identifiers
|
|
if len(config.Agent.ID) > 64 {
|
|
return fmt.Errorf("agent ID too long (max 64 characters): %s", config.Agent.ID)
|
|
}
|
|
if !isAlphanumericWithDashes(config.Agent.ID) {
|
|
return fmt.Errorf("agent ID contains invalid characters: %s", config.Agent.ID)
|
|
}
|
|
}
|
|
|
|
// Validate specialization
|
|
if config.Agent.Specialization != "" {
|
|
if len(config.Agent.Specialization) > 64 {
|
|
return fmt.Errorf("specialization too long (max 64 characters)")
|
|
}
|
|
if !isAlphanumericWithUnderscore(config.Agent.Specialization) {
|
|
return fmt.Errorf("specialization contains invalid characters: %s", config.Agent.Specialization)
|
|
}
|
|
}
|
|
|
|
// Validate role
|
|
if config.Agent.Role != "" {
|
|
if len(config.Agent.Role) > 32 {
|
|
return fmt.Errorf("role too long (max 32 characters)")
|
|
}
|
|
if !isAlphanumericWithUnderscore(config.Agent.Role) {
|
|
return fmt.Errorf("role contains invalid characters: %s", config.Agent.Role)
|
|
}
|
|
}
|
|
|
|
// Validate capabilities list
|
|
if len(config.Agent.Capabilities) > 20 {
|
|
return fmt.Errorf("too many capabilities (max 20)")
|
|
}
|
|
for _, capability := range config.Agent.Capabilities {
|
|
if len(capability) > 32 {
|
|
return fmt.Errorf("capability name too long (max 32 characters): %s", capability)
|
|
}
|
|
if !isAlphanumericWithUnderscore(capability) {
|
|
return fmt.Errorf("capability contains invalid characters: %s", capability)
|
|
}
|
|
}
|
|
|
|
// Validate models list
|
|
if len(config.Agent.Models) > 50 {
|
|
return fmt.Errorf("too many models (max 50)")
|
|
}
|
|
for _, model := range config.Agent.Models {
|
|
if len(model) > 64 {
|
|
return fmt.Errorf("model name too long (max 64 characters): %s", model)
|
|
}
|
|
if !isAlphanumericWithDots(model) {
|
|
return fmt.Errorf("model name contains invalid characters: %s", model)
|
|
}
|
|
}
|
|
|
|
// Validate URLs and webhooks
|
|
if config.WHOOSHAPI.BaseURL != "" {
|
|
if err := validateURL(config.WHOOSHAPI.BaseURL); err != nil {
|
|
return fmt.Errorf("invalid WHOOSH API URL: %w", err)
|
|
}
|
|
}
|
|
|
|
if config.Agent.ModelSelectionWebhook != "" {
|
|
if err := validateURL(config.Agent.ModelSelectionWebhook); err != nil {
|
|
return fmt.Errorf("invalid model selection webhook URL: %w", err)
|
|
}
|
|
}
|
|
|
|
if config.P2P.EscalationWebhook != "" {
|
|
if err := validateURL(config.P2P.EscalationWebhook); err != nil {
|
|
return fmt.Errorf("invalid escalation webhook URL: %w", err)
|
|
}
|
|
}
|
|
|
|
// Validate file paths
|
|
if config.GitHub.TokenFile != "" {
|
|
if err := validator.ValidateFilePath(config.GitHub.TokenFile); err != nil {
|
|
return fmt.Errorf("invalid GitHub token file path: %w", err)
|
|
}
|
|
}
|
|
|
|
if config.Security.AuditPath != "" {
|
|
if err := validator.ValidateFilePath(config.Security.AuditPath); err != nil {
|
|
return fmt.Errorf("invalid audit path: %w", err)
|
|
}
|
|
}
|
|
|
|
// Validate numeric limits
|
|
if config.Agent.MaxTasks < 1 || config.Agent.MaxTasks > 100 {
|
|
return fmt.Errorf("invalid max tasks: must be between 1 and 100, got %d", config.Agent.MaxTasks)
|
|
}
|
|
|
|
if config.WHOOSHAPI.RetryCount < 0 || config.WHOOSHAPI.RetryCount > 10 {
|
|
return fmt.Errorf("invalid retry count: must be between 0 and 10, got %d", config.WHOOSHAPI.RetryCount)
|
|
}
|
|
|
|
if config.Security.KeyRotationDays < 0 || config.Security.KeyRotationDays > 365 {
|
|
return fmt.Errorf("invalid key rotation days: must be between 0 and 365, got %d", config.Security.KeyRotationDays)
|
|
}
|
|
|
|
// Validate timeout durations (prevent extremely long timeouts that could cause DoS)
|
|
maxTimeout := 300 * time.Second // 5 minutes max
|
|
if config.WHOOSHAPI.Timeout > maxTimeout {
|
|
return fmt.Errorf("WHOOSH API timeout too long (max 5 minutes): %v", config.WHOOSHAPI.Timeout)
|
|
}
|
|
if config.GitHub.Timeout > maxTimeout {
|
|
return fmt.Errorf("GitHub timeout too long (max 5 minutes): %v", config.GitHub.Timeout)
|
|
}
|
|
if config.Agent.PollInterval > 3600*time.Second {
|
|
return fmt.Errorf("poll interval too long (max 1 hour): %v", config.Agent.PollInterval)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Helper validation functions
|
|
|
|
func isAlphanumericWithDashes(s string) bool {
|
|
if len(s) == 0 {
|
|
return false
|
|
}
|
|
for _, char := range s {
|
|
if !((char >= 'a' && char <= 'z') || (char >= 'A' && char <= 'Z') ||
|
|
(char >= '0' && char <= '9') || char == '-' || char == '_') {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func isAlphanumericWithUnderscore(s string) bool {
|
|
if len(s) == 0 {
|
|
return false
|
|
}
|
|
for _, char := range s {
|
|
if !((char >= 'a' && char <= 'z') || (char >= 'A' && char <= 'Z') ||
|
|
(char >= '0' && char <= '9') || char == '_') {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func isAlphanumericWithDots(s string) bool {
|
|
if len(s) == 0 {
|
|
return false
|
|
}
|
|
for _, char := range s {
|
|
if !((char >= 'a' && char <= 'z') || (char >= 'A' && char <= 'Z') ||
|
|
(char >= '0' && char <= '9') || char == '.' || char == '-' || char == '_') {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func validateURL(url string) error {
|
|
if len(url) > 2048 {
|
|
return fmt.Errorf("URL too long (max 2048 characters)")
|
|
}
|
|
|
|
// Basic URL validation - should start with http:// or https://
|
|
if !strings.HasPrefix(url, "http://") && !strings.HasPrefix(url, "https://") {
|
|
return fmt.Errorf("URL must start with http:// or https://")
|
|
}
|
|
|
|
// Check for dangerous characters that could be used for injection
|
|
dangerousChars := []string{"`", "$", ";", "|", "&", "<", ">", "\n", "\r"}
|
|
for _, char := range dangerousChars {
|
|
if strings.Contains(url, char) {
|
|
return fmt.Errorf("URL contains dangerous characters")
|
|
}
|
|
}
|
|
|
|
return nil
|
|
} |