Files
rs_website/internal/checker/checker.go

89 lines
1.9 KiB
Go

// Package checker periodically HTTP-checks services and updates status.json.
package checker
import (
"log"
"net/http"
"path/filepath"
"time"
"ridgwaysystems.org/website/internal/status"
)
// Start launches the background checker. It checks services every interval
// and updates status.json in dataDir. Services without a CheckURL are skipped.
func Start(dataDir string, interval time.Duration) {
go run(dataDir, interval)
}
func run(dataDir string, interval time.Duration) {
client := &http.Client{
Timeout: 10 * time.Second,
CheckRedirect: func(req *http.Request, via []*http.Request) error {
if len(via) >= 3 {
return http.ErrUseLastResponse
}
return nil
},
}
path := filepath.Join(dataDir, "status.json")
for {
check(client, path)
time.Sleep(interval)
}
}
func check(client *http.Client, path string) {
page, err := status.Load(path)
if err != nil {
log.Printf("checker: load %s: %v", path, err)
return
}
changed := false
for i, svc := range page.Services {
if svc.CheckURL == "" {
continue
}
prev := svc.Status
newStatus := probe(client, svc.CheckURL)
if newStatus != prev {
log.Printf("checker: %s %s → %s", svc.Name, prev, newStatus)
page.Services[i].Status = newStatus
changed = true
}
}
if changed {
if err := status.Save(path, page); err != nil {
log.Printf("checker: save %s: %v", path, err)
}
} else {
// Still update last_checked timestamp so the status page shows freshness
page.LastChecked = time.Now().UTC()
if err := status.Save(path, page); err != nil {
log.Printf("checker: save %s: %v", path, err)
}
}
}
func probe(client *http.Client, url string) string {
resp, err := client.Get(url)
if err != nil {
return "down"
}
defer resp.Body.Close()
switch {
case resp.StatusCode < 400:
return "up"
case resp.StatusCode >= 500:
return "down"
default:
// 4xx could mean the service is up but the URL is wrong; treat as degraded
return "degraded"
}
}