Files
rs_website/internal/checker/checker.go
2026-03-27 07:57:13 -05:00

98 lines
2.2 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"
"ridgwaysystems.org/website/internal/uptime"
)
// 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
},
}
statusPath := filepath.Join(dataDir, "status.json")
uptimePath := filepath.Join(dataDir, "uptime.json")
for {
check(client, statusPath, uptimePath)
time.Sleep(interval)
}
}
func check(client *http.Client, statusPath, uptimePath string) {
page, err := status.Load(statusPath)
if err != nil {
log.Printf("checker: load %s: %v", statusPath, 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(statusPath, page); err != nil {
log.Printf("checker: save %s: %v", statusPath, err)
}
} else {
page.LastChecked = time.Now().UTC()
if err := status.Save(statusPath, page); err != nil {
log.Printf("checker: save %s: %v", statusPath, err)
}
}
// Record hourly uptime snapshot.
statuses := make(map[string]string, len(page.Services))
for _, svc := range page.Services {
statuses[svc.Name] = svc.Status
}
if err := uptime.Record(uptimePath, statuses); err != nil {
log.Printf("checker: uptime record: %v", 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:
return "degraded"
}
}