package collector import ( "encoding/json" "io" "net/http" "os" "os/exec" "strings" "time" ) type CaddySite struct { Domain string `json:"domain"` Upstream string `json:"upstream"` TLS bool `json:"tls"` } type CaddyUpstream struct { Address string `json:"address"` NumRequests int `json:"num_requests"` Fails int `json:"fails"` Healthy bool `json:"healthy"` } type CaddyData struct { Version string `json:"version"` Sites []CaddySite `json:"sites"` Upstreams []CaddyUpstream `json:"upstreams,omitempty"` } const caddyFile = "/usr/local/etc/caddy/Caddyfile" const caddyAdminAPI = "http://127.0.0.1:2019" func CollectCaddy() *CaddyData { if _, err := os.Stat(caddyFile); err != nil { return nil } data := &CaddyData{} // Version if out, err := exec.Command("/usr/local/bin/caddy", "version").Output(); err == nil { fields := strings.Fields(strings.TrimSpace(string(out))) if len(fields) > 0 { data.Version = fields[0] } } // Parse Caddyfile fuer Sites content, err := os.ReadFile(caddyFile) if err != nil { return data } lines := strings.Split(string(content), "\n") var currentDomain string var currentTLS bool depth := 0 for _, line := range lines { trimmed := strings.TrimSpace(line) if trimmed == "" || strings.HasPrefix(trimmed, "#") { continue } if depth == 0 && strings.Contains(trimmed, "{") { parts := strings.Fields(trimmed) if len(parts) >= 2 && parts[len(parts)-1] == "{" { currentDomain = parts[0] currentTLS = !strings.HasPrefix(currentDomain, "http://") depth = 1 continue } } if depth > 0 { depth += strings.Count(trimmed, "{") - strings.Count(trimmed, "}") if strings.HasPrefix(trimmed, "reverse_proxy") { parts := strings.Fields(trimmed) if len(parts) >= 2 { upstream := parts[1] data.Sites = append(data.Sites, CaddySite{ Domain: currentDomain, Upstream: upstream, TLS: currentTLS, }) } } if depth <= 0 { currentDomain = "" depth = 0 } } } // Admin-API: Upstream-Statistiken data.Upstreams = collectCaddyUpstreams() return data } func collectCaddyUpstreams() []CaddyUpstream { client := &http.Client{Timeout: 3 * time.Second} resp, err := client.Get(caddyAdminAPI + "/api/reverse_proxy/upstreams") if err != nil { return nil } defer resp.Body.Close() body, err := io.ReadAll(io.LimitReader(resp.Body, 64*1024)) if err != nil { return nil } // Admin-API liefert: [{"address":"host:port","num_requests":N,"fails":N}] var raw []struct { Address string `json:"address"` NumRequests int `json:"num_requests"` Fails int `json:"fails"` } if err := json.Unmarshal(body, &raw); err != nil { return nil } upstreams := make([]CaddyUpstream, 0, len(raw)) for _, u := range raw { upstreams = append(upstreams, CaddyUpstream{ Address: u.Address, NumRequests: u.NumRequests, Fails: u.Fails, Healthy: u.Fails == 0, }) } return upstreams }