139 lines
3.0 KiB
Go

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
}