package v1 import ( "fmt" "log/slog" "net/http" "github.com/wild-cloud/wild-central/internal/config" "github.com/wild-cloud/wild-central/internal/haproxy" ) // HaproxyStatus returns the status of the HAProxy service and current instance routes. func (api *API) HaproxyStatus(w http.ResponseWriter, r *http.Request) { status, err := api.haproxy.GetStatus() if err != nil { respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to get HAProxy status: %v", err)) return } routes, _ := api.buildInstanceRoutes() respondJSON(w, http.StatusOK, map[string]any{ "status": status.Status, "pid": status.PID, "configFile": status.ConfigFile, "routes": routes, }) } // HaproxyGetConfig returns the current HAProxy configuration file contents func (api *API) HaproxyGetConfig(w http.ResponseWriter, r *http.Request) { content, err := api.haproxy.ReadConfig() if err != nil { respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to read HAProxy config: %v", err)) return } respondJSON(w, http.StatusOK, map[string]any{ "configFile": api.haproxy.GetConfigPath(), "content": content, }) } // HaproxyRestart restarts the HAProxy service func (api *API) HaproxyRestart(w http.ResponseWriter, r *http.Request) { if err := api.haproxy.RestartService(); err != nil { respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to restart HAProxy: %v", err)) return } api.broadcastHaproxyEvent("haproxy:restart", "HAProxy service restarted") respondJSON(w, http.StatusOK, map[string]string{ "message": "HAProxy service restarted successfully", }) } // HaproxyGenerate regenerates HAProxy config from all WC instance data and custom routes, // then updates nftables to match. Both operations happen together so ports stay in sync. func (api *API) HaproxyGenerate(w http.ResponseWriter, r *http.Request) { routes, customRoutes, configContent, err := api.syncHAProxy() if err != nil { respondError(w, http.StatusInternalServerError, err.Error()) return } respondJSON(w, http.StatusOK, map[string]any{ "message": "HAProxy configuration generated and applied successfully", "routes": routes, "customRoutes": len(customRoutes), "config": configContent, }) } // syncHAProxy regenerates and applies the HAProxy and nftables configuration. // Returns the routes, custom routes, and config content on success. func (api *API) syncHAProxy() ([]haproxy.InstanceRoute, []haproxy.CustomRoute, string, error) { routes, err := api.buildInstanceRoutes() if err != nil { return nil, nil, "", fmt.Errorf("failed to list instances: %w", err) } globalCfg, err := config.LoadGlobalConfig(api.getGlobalConfigPath()) if err != nil { return nil, nil, "", fmt.Errorf("failed to load global config: %w", err) } var customRoutes []haproxy.CustomRoute for _, cr := range globalCfg.Cloud.HAProxy.CustomRoutes { customRoutes = append(customRoutes, haproxy.CustomRoute{ Name: cr.Name, Port: cr.Port, Backend: cr.Backend, }) } configContent := api.haproxy.Generate(routes, customRoutes, globalCfg.Cloud.Central.Domain) if err := api.haproxy.WriteConfig(configContent); err != nil { return nil, nil, "", fmt.Errorf("failed to write HAProxy config: %w", err) } if err := api.haproxy.ReloadService(); err != nil { return nil, nil, "", fmt.Errorf("failed to reload HAProxy: %w", err) } extraTCP, extraUDP := config.SplitExtraPorts(globalCfg.Cloud.Nftables.ExtraPorts) extraUDP = append(extraUDP, api.vpnAutoUDPPorts()...) ports := api.haproxy.GetListenPorts(routes, customRoutes) nftContent := api.nftables.Generate(ports, extraTCP, extraUDP, globalCfg.Cloud.Nftables.WANInterface) if err := api.nftables.WriteRules(nftContent); err != nil { slog.Error("failed to update nftables rules", "component", "haproxy-sync", "error", err) } else if err := api.nftables.ApplyRules(); err != nil { slog.Error("failed to apply nftables rules", "component", "haproxy-sync", "error", err) } api.broadcastHaproxyEvent("haproxy:config", "HAProxy configuration updated and applied") return routes, customRoutes, configContent, nil } // HaproxyStats returns live per-backend connection stats from the HAProxy stats socket func (api *API) HaproxyStats(w http.ResponseWriter, r *http.Request) { stats, err := api.haproxy.GetStats() if err != nil { respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to get HAProxy stats: %v", err)) return } respondJSON(w, http.StatusOK, map[string]any{"backends": stats}) } // buildInstanceRoutes returns haproxy.InstanceRoute entries. // TODO: Instance routes will be populated via service registration from Wild Cloud instances. // For now, returns an empty list — Central doesn't manage instances directly. func (api *API) buildInstanceRoutes() ([]haproxy.InstanceRoute, error) { return nil, nil }