// 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" } }