package v1 import ( "encoding/json" "fmt" "net/http" "github.com/gorilla/mux" "github.com/wild-cloud/wild-central/internal/config" "github.com/wild-cloud/wild-central/internal/wireguard" ) // VpnStatus returns the current WireGuard interface status. func (api *API) VpnStatus(w http.ResponseWriter, r *http.Request) { status, err := api.vpn.GetStatus() if err != nil { respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to get VPN status: %v", err)) return } respondJSON(w, http.StatusOK, status) } // VpnGetConfig returns the server interface configuration and public key. func (api *API) VpnGetConfig(w http.ResponseWriter, r *http.Request) { cfg, err := api.vpn.GetConfig() if err != nil { respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to get VPN config: %v", err)) return } respondJSON(w, http.StatusOK, map[string]any{ "enabled": cfg.Enabled, "listenPort": cfg.ListenPort, "address": cfg.Address, "endpoint": cfg.Endpoint, "dns": cfg.DNS, "lanCIDR": cfg.LanCIDR, "publicKey": api.vpn.GetPublicKey(), }) } // VpnUpdateConfig updates the server interface configuration. func (api *API) VpnUpdateConfig(w http.ResponseWriter, r *http.Request) { var req wireguard.Config if err := json.NewDecoder(r.Body).Decode(&req); err != nil { respondError(w, http.StatusBadRequest, fmt.Sprintf("Invalid request: %v", err)) return } if req.ListenPort == 0 { req.ListenPort = 51820 } if err := api.vpn.SaveConfig(&req); err != nil { respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to save VPN config: %v", err)) return } // Resync nftables so the VPN listen port is automatically allowed/removed if globalCfg, err := config.LoadState(api.statePath()); err == nil { go api.syncNftablesOnly(globalCfg) } respondJSON(w, http.StatusOK, map[string]string{"message": "VPN configuration saved"}) } // VpnGenerateKeypair generates a new server keypair. func (api *API) VpnGenerateKeypair(w http.ResponseWriter, r *http.Request) { if err := api.vpn.GenerateKeypair(); err != nil { respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to generate keypair: %v", err)) return } respondJSON(w, http.StatusOK, map[string]string{ "message": "Server keypair generated", "publicKey": api.vpn.GetPublicKey(), }) } // VpnApply writes the wg0.conf and brings the WireGuard interface up. func (api *API) VpnApply(w http.ResponseWriter, r *http.Request) { if err := api.vpn.Apply(); err != nil { respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to apply VPN config: %v", err)) return } respondJSON(w, http.StatusOK, map[string]string{"message": "WireGuard interface applied successfully"}) } // VpnListPeers returns all configured peers (without private keys). func (api *API) VpnListPeers(w http.ResponseWriter, r *http.Request) { peers, err := api.vpn.ListPeers() if err != nil { respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to list peers: %v", err)) return } respondJSON(w, http.StatusOK, map[string]any{"peers": redactPeers(peers)}) } // VpnAddPeer adds a new peer, auto-generating a keypair and assigning an IP. func (api *API) VpnAddPeer(w http.ResponseWriter, r *http.Request) { var req struct { Name string `json:"name"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil || req.Name == "" { respondError(w, http.StatusBadRequest, "name is required") return } peer, err := api.vpn.AddPeer(req.Name) if err != nil { respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to add peer: %v", err)) return } respondJSON(w, http.StatusCreated, redactPeer(peer)) } // VpnGetPeerConfig returns the wg-quick config text for a peer (used by clients and QR generation). func (api *API) VpnGetPeerConfig(w http.ResponseWriter, r *http.Request) { id := mux.Vars(r)["id"] text, err := api.vpn.GeneratePeerConfigText(id) if err != nil { respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to generate peer config: %v", err)) return } peer, err := api.vpn.GetPeer(id) if err != nil { respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to get peer: %v", err)) return } respondJSON(w, http.StatusOK, map[string]string{ "name": peer.Name, "config": text, }) } // VpnDeletePeer removes a peer by ID. func (api *API) VpnDeletePeer(w http.ResponseWriter, r *http.Request) { id := mux.Vars(r)["id"] if err := api.vpn.DeletePeer(id); err != nil { respondError(w, http.StatusNotFound, fmt.Sprintf("Failed to delete peer: %v", err)) return } respondJSON(w, http.StatusOK, map[string]string{"message": "Peer deleted"}) } // redactPeers strips private keys from a peer list before sending to clients. func redactPeers(peers []*wireguard.Peer) []map[string]any { out := make([]map[string]any, len(peers)) for i, p := range peers { out[i] = redactPeer(p) } return out } func redactPeer(p *wireguard.Peer) map[string]any { return map[string]any{ "id": p.ID, "name": p.Name, "publicKey": p.PublicKey, "allowedIPs": p.AllowedIPs, "createdAt": p.CreatedAt, } }