package collector import ( "os" "os/exec" "strings" ) type CaddySite struct { Domain string `json:"domain"` Upstream string `json:"upstream"` TLS bool `json:"tls"` } type CaddyData struct { Version string `json:"version"` Sites []CaddySite `json:"sites"` } const caddyFile = "/usr/local/etc/caddy/Caddyfile" 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 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 } // Check for domain block start: "domain.com {" or "http://domain.com {" 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 { // Track nested braces 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 } } } return data }