package server import ( "encoding/json" "fmt" "net/http" "strings" "time" "github.com/rs/zerolog/log" ) // BootstrapPeer represents a libp2p bootstrap peer for CHORUS agent discovery type BootstrapPeer struct { Multiaddr string `json:"multiaddr"` // libp2p multiaddr format: /ip4/{ip}/tcp/{port}/p2p/{peer_id} PeerID string `json:"peer_id"` // libp2p peer ID Name string `json:"name"` // Human-readable name Priority int `json:"priority"` // Priority order (1 = highest) } // HandleBootstrapPeers returns list of bootstrap peers for CHORUS agent discovery // GET /api/bootstrap-peers // // This endpoint provides a dynamic list of bootstrap peers that new CHORUS agents // should connect to when joining the P2P mesh. The list includes: // 1. HMMM monitor (priority 1) - For traffic observation // 2. First 3 stable agents (priority 2-4) - For mesh formation // // Response format: // { // "bootstrap_peers": [ // { // "multiaddr": "/ip4/172.27.0.6/tcp/9001/p2p/12D3Koo...", // "peer_id": "12D3Koo...", // "name": "hmmm-monitor", // "priority": 1 // } // ], // "updated_at": "2025-01-15T10:30:00Z" // } func (s *Server) HandleBootstrapPeers(w http.ResponseWriter, r *http.Request) { log.Info().Msg("📡 Bootstrap peers requested") var bootstrapPeers []BootstrapPeer // Get ALL connected agents from discovery - return complete dynamic list // This allows new agents AND the hmmm-monitor to discover the P2P mesh agents := s.p2pDiscovery.GetAgents() log.Debug().Int("total_agents", len(agents)).Msg("Discovered agents for bootstrap list") // HTTP client for fetching agent health endpoints client := &http.Client{Timeout: 5 * time.Second} for priority, agent := range agents { if agent.Endpoint == "" { log.Warn().Str("agent", agent.ID).Msg("Agent has no endpoint, skipping") continue } // Query agent health endpoint to get peer_id and multiaddrs healthURL := fmt.Sprintf("%s/api/health", strings.TrimRight(agent.Endpoint, "/")) log.Debug().Str("agent", agent.ID).Str("health_url", healthURL).Msg("Fetching agent health") resp, err := client.Get(healthURL) if err != nil { log.Warn().Str("agent", agent.ID).Err(err).Msg("Failed to fetch agent health") continue } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { log.Warn().Str("agent", agent.ID).Int("status", resp.StatusCode).Msg("Agent health check failed") continue } var health struct { PeerID string `json:"peer_id"` Multiaddrs []string `json:"multiaddrs"` } if err := json.NewDecoder(resp.Body).Decode(&health); err != nil { log.Warn().Str("agent", agent.ID).Err(err).Msg("Failed to decode health response") continue } // Add only the first multiaddr per agent to avoid duplicates // Each agent may have multiple interfaces but we only need one for bootstrap if len(health.Multiaddrs) > 0 { bootstrapPeers = append(bootstrapPeers, BootstrapPeer{ Multiaddr: health.Multiaddrs[0], PeerID: health.PeerID, Name: agent.ID, Priority: priority + 1, }) log.Debug(). Str("agent_id", agent.ID). Str("peer_id", health.PeerID). Str("multiaddr", health.Multiaddrs[0]). Int("priority", priority+1). Msg("Added agent to bootstrap list") } } response := map[string]interface{}{ "bootstrap_peers": bootstrapPeers, "updated_at": time.Now(), "count": len(bootstrapPeers), } w.Header().Set("Content-Type", "application/json") if err := json.NewEncoder(w).Encode(response); err != nil { log.Error().Err(err).Msg("Failed to encode bootstrap peers response") http.Error(w, "Internal server error", http.StatusInternalServerError) return } log.Info(). Int("peer_count", len(bootstrapPeers)). Msg("✅ Bootstrap peers list returned") }