Enhance deployment system with retry functionality and improved UX
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>
This commit is contained in:
204
internal/common/runtime/config.go
Normal file
204
internal/common/runtime/config.go
Normal file
@@ -0,0 +1,204 @@
|
||||
package runtime
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"chorus.services/bzzz/pkg/config"
|
||||
)
|
||||
|
||||
// ConfigValidator validates configuration for specific binary types
|
||||
type ConfigValidator struct {
|
||||
binaryType BinaryType
|
||||
}
|
||||
|
||||
// NewConfigValidator creates a new config validator
|
||||
func NewConfigValidator(binaryType BinaryType) *ConfigValidator {
|
||||
return &ConfigValidator{
|
||||
binaryType: binaryType,
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateForBinary validates configuration for the specified binary type
|
||||
func (v *ConfigValidator) ValidateForBinary(cfg *config.Config) error {
|
||||
// Common validation
|
||||
if err := v.validateCommonConfig(cfg); err != nil {
|
||||
return fmt.Errorf("common config validation failed: %w", err)
|
||||
}
|
||||
|
||||
// Binary-specific validation
|
||||
switch v.binaryType {
|
||||
case BinaryTypeAgent:
|
||||
return v.validateAgentConfig(cfg)
|
||||
case BinaryTypeHAP:
|
||||
return v.validateHAPConfig(cfg)
|
||||
default:
|
||||
return fmt.Errorf("unknown binary type: %v", v.binaryType)
|
||||
}
|
||||
}
|
||||
|
||||
// validateCommonConfig validates common configuration for all binary types
|
||||
func (v *ConfigValidator) validateCommonConfig(cfg *config.Config) error {
|
||||
if cfg == nil {
|
||||
return fmt.Errorf("configuration is nil")
|
||||
}
|
||||
|
||||
// Validate agent configuration
|
||||
if cfg.Agent.ID == "" {
|
||||
return fmt.Errorf("agent ID is required")
|
||||
}
|
||||
|
||||
// Validate basic capabilities
|
||||
if len(cfg.Agent.Capabilities) == 0 {
|
||||
return fmt.Errorf("at least one capability is required")
|
||||
}
|
||||
|
||||
// Validate P2P configuration if needed
|
||||
if cfg.V2.P2P.Enabled {
|
||||
if cfg.V2.P2P.ListenPort < 1024 || cfg.V2.P2P.ListenPort > 65535 {
|
||||
return fmt.Errorf("invalid P2P listen port: %d", cfg.V2.P2P.ListenPort)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateAgentConfig validates agent-specific configuration
|
||||
func (v *ConfigValidator) validateAgentConfig(cfg *config.Config) error {
|
||||
// Agent needs models for task execution
|
||||
if len(cfg.Agent.Models) == 0 {
|
||||
return fmt.Errorf("agent requires at least one model")
|
||||
}
|
||||
|
||||
// Agent needs specialization
|
||||
if cfg.Agent.Specialization == "" {
|
||||
return fmt.Errorf("agent specialization is required")
|
||||
}
|
||||
|
||||
// Validate max tasks
|
||||
if cfg.Agent.MaxTasks <= 0 {
|
||||
return fmt.Errorf("agent max_tasks must be greater than 0")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateHAPConfig validates HAP-specific configuration
|
||||
func (v *ConfigValidator) validateHAPConfig(cfg *config.Config) error {
|
||||
// HAP has different requirements than agent
|
||||
// Models are optional for HAP (it facilitates human interaction)
|
||||
|
||||
// HAP should have role configuration for proper P2P participation
|
||||
if cfg.Agent.Role == "" {
|
||||
return fmt.Errorf("HAP requires a role for P2P participation")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateMultiBinaryDeployment validates that agent and HAP configs are compatible
|
||||
func ValidateMultiBinaryDeployment(agentConfig, hapConfig *config.Config) error {
|
||||
validators := []func(*config.Config, *config.Config) error{
|
||||
validateP2PCompatibility,
|
||||
validatePortAssignments,
|
||||
validateAgentIdentities,
|
||||
validateEncryptionKeys,
|
||||
}
|
||||
|
||||
for _, validator := range validators {
|
||||
if err := validator(agentConfig, hapConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateP2PCompatibility ensures both configs can participate in same P2P mesh
|
||||
func validateP2PCompatibility(agentConfig, hapConfig *config.Config) error {
|
||||
// Check P2P network compatibility
|
||||
if agentConfig.V2.P2P.NetworkID != hapConfig.V2.P2P.NetworkID {
|
||||
return fmt.Errorf("P2P network ID mismatch: agent=%s, hap=%s",
|
||||
agentConfig.V2.P2P.NetworkID, hapConfig.V2.P2P.NetworkID)
|
||||
}
|
||||
|
||||
// Check bootstrap peers compatibility
|
||||
if len(agentConfig.V2.DHT.BootstrapPeers) != len(hapConfig.V2.DHT.BootstrapPeers) {
|
||||
return fmt.Errorf("bootstrap peers configuration differs between agent and HAP")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validatePortAssignments ensures no port conflicts
|
||||
func validatePortAssignments(agentConfig, hapConfig *config.Config) error {
|
||||
// Check HTTP ports
|
||||
if agentConfig.V2.API.Port == hapConfig.V2.API.Port {
|
||||
return fmt.Errorf("HTTP port conflict: both configs use port %d", agentConfig.V2.API.Port)
|
||||
}
|
||||
|
||||
// Check P2P ports
|
||||
if agentConfig.V2.P2P.ListenPort == hapConfig.V2.P2P.ListenPort {
|
||||
return fmt.Errorf("P2P port conflict: both configs use port %d", agentConfig.V2.P2P.ListenPort)
|
||||
}
|
||||
|
||||
// Check UCXI ports if enabled
|
||||
if agentConfig.UCXL.Enabled && hapConfig.UCXL.Enabled {
|
||||
if agentConfig.UCXL.Server.Port == hapConfig.UCXL.Server.Port {
|
||||
return fmt.Errorf("UCXI port conflict: both configs use port %d", agentConfig.UCXL.Server.Port)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateAgentIdentities ensures agent IDs don't conflict
|
||||
func validateAgentIdentities(agentConfig, hapConfig *config.Config) error {
|
||||
if agentConfig.Agent.ID == hapConfig.Agent.ID {
|
||||
return fmt.Errorf("agent ID conflict: both configs use ID %s", agentConfig.Agent.ID)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateEncryptionKeys ensures encryption compatibility
|
||||
func validateEncryptionKeys(agentConfig, hapConfig *config.Config) error {
|
||||
// Both should use same encryption settings for compatibility
|
||||
if agentConfig.V2.Security.EncryptionEnabled != hapConfig.V2.Security.EncryptionEnabled {
|
||||
return fmt.Errorf("encryption settings mismatch")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckForRunningInstance checks if another instance is already running
|
||||
func CheckForRunningInstance(agentID string, binaryType BinaryType) error {
|
||||
lockFile := fmt.Sprintf("/tmp/bzzz-%s-%s.lock", agentID, binaryType)
|
||||
|
||||
if _, err := os.Stat(lockFile); err == nil {
|
||||
return fmt.Errorf("instance already running: %s %s", binaryType, agentID)
|
||||
}
|
||||
|
||||
// Create lock file
|
||||
return os.WriteFile(lockFile, []byte(fmt.Sprintf("%d", os.Getpid())), 0644)
|
||||
}
|
||||
|
||||
// RemoveInstanceLock removes the instance lock file
|
||||
func RemoveInstanceLock(agentID string, binaryType BinaryType) error {
|
||||
lockFile := fmt.Sprintf("/tmp/bzzz-%s-%s.lock", agentID, binaryType)
|
||||
return os.Remove(lockFile)
|
||||
}
|
||||
|
||||
// GetConfigPath determines the configuration file path
|
||||
func GetConfigPath() string {
|
||||
configPath := os.Getenv("BZZZ_CONFIG_PATH")
|
||||
if configPath == "" {
|
||||
configPath = ".bzzz/config.yaml"
|
||||
}
|
||||
return configPath
|
||||
}
|
||||
|
||||
// NeedsSetup checks if the system needs to run setup mode
|
||||
func NeedsSetup() bool {
|
||||
configPath := GetConfigPath()
|
||||
return config.IsSetupRequired(configPath)
|
||||
}
|
||||
Reference in New Issue
Block a user