feat: Extract Wild Central as standalone Go service
Extract the Central networking functionality from wild-cloud/api into a standalone service. Wild Central manages DNS (dnsmasq), gateway (HAProxy), firewall (nftables), VPN (WireGuard), TLS (certbot), security (CrowdSec), and DDNS — all the network appliance concerns. All Cloud-specific code (instances, clusters, nodes, apps, backups, operations, kubectl/talosctl tooling) has been removed. The API struct and route registration contain only Central endpoints. Tests updated to match the new API signature. Builds and all tests pass. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
103
internal/api/v1/handlers_certbot.go
Normal file
103
internal/api/v1/handlers_certbot.go
Normal file
@@ -0,0 +1,103 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/wild-cloud/wild-central/internal/config"
|
||||
)
|
||||
|
||||
// CertStatus returns the TLS certificate status for the central domain.
|
||||
func (api *API) CertStatus(w http.ResponseWriter, r *http.Request) {
|
||||
domain := api.getCentralDomain()
|
||||
if domain == "" {
|
||||
respondJSON(w, http.StatusOK, map[string]any{
|
||||
"configured": false,
|
||||
"message": "No central domain configured",
|
||||
})
|
||||
return
|
||||
}
|
||||
status := api.certbot.GetStatus(domain)
|
||||
respondJSON(w, http.StatusOK, map[string]any{
|
||||
"configured": true,
|
||||
"domain": domain,
|
||||
"cert": status,
|
||||
})
|
||||
}
|
||||
|
||||
// CertProvision provisions a TLS certificate for the central domain using DNS-01.
|
||||
func (api *API) CertProvision(w http.ResponseWriter, r *http.Request) {
|
||||
domain := api.getCentralDomain()
|
||||
if domain == "" {
|
||||
respondError(w, http.StatusBadRequest, "No central domain configured in global config")
|
||||
return
|
||||
}
|
||||
|
||||
globalCfg, err := config.LoadGlobalConfig(api.getGlobalConfigPath())
|
||||
if err != nil {
|
||||
respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to load config: %v", err))
|
||||
return
|
||||
}
|
||||
email := globalCfg.Operator.Email
|
||||
if email == "" {
|
||||
respondError(w, http.StatusBadRequest, "Operator email not configured")
|
||||
return
|
||||
}
|
||||
|
||||
// Get Cloudflare API token
|
||||
token := api.getCloudflareToken()
|
||||
if token == "" {
|
||||
respondError(w, http.StatusBadRequest, "Cloudflare API token not configured — set it on the Cloudflare page")
|
||||
return
|
||||
}
|
||||
|
||||
// Write credentials file for certbot
|
||||
if err := api.certbot.EnsureCredentials(token); err != nil {
|
||||
respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to write credentials: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
// Provision the certificate. The deploy hook builds the HAProxy PEM and reloads HAProxy.
|
||||
if err := api.certbot.Provision(domain, email); err != nil {
|
||||
respondError(w, http.StatusInternalServerError, fmt.Sprintf("Certificate provisioning failed: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
// Regenerate HAProxy config to ensure central domain SNI routing is active
|
||||
if _, _, _, err := api.syncHAProxy(); err != nil {
|
||||
respondError(w, http.StatusInternalServerError, fmt.Sprintf("Certificate provisioned but HAProxy config update failed: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
status := api.certbot.GetStatus(domain)
|
||||
respondJSON(w, http.StatusOK, map[string]any{
|
||||
"message": "Certificate provisioned successfully",
|
||||
"cert": status,
|
||||
})
|
||||
}
|
||||
|
||||
// CertRenew renews all certbot-managed certificates.
|
||||
func (api *API) CertRenew(w http.ResponseWriter, r *http.Request) {
|
||||
if err := api.certbot.Renew(); err != nil {
|
||||
respondError(w, http.StatusInternalServerError, fmt.Sprintf("Certificate renewal failed: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
// Rebuild HAProxy PEM for any renewed certs
|
||||
domain := api.getCentralDomain()
|
||||
if domain != "" {
|
||||
_ = api.certbot.BuildHAProxyCert(domain)
|
||||
_, _, _, _ = api.syncHAProxy()
|
||||
}
|
||||
|
||||
respondJSON(w, http.StatusOK, map[string]string{"message": "Certificates renewed"})
|
||||
}
|
||||
|
||||
// getCentralDomain returns the configured central domain or empty string.
|
||||
func (api *API) getCentralDomain() string {
|
||||
globalCfg, err := config.LoadGlobalConfig(api.getGlobalConfigPath())
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return globalCfg.Cloud.Central.Domain
|
||||
}
|
||||
Reference in New Issue
Block a user