add betteruptime support
Some checks failed
Build and Push Docker Image to GHCR / build-and-push (push) Has been cancelled

This commit is contained in:
Zoe
2026-01-17 23:50:07 -06:00
parent 3ffd439f88
commit 74d42acb59
4 changed files with 116 additions and 31 deletions

View File

@@ -10,8 +10,6 @@ on:
jobs:
build-and-push:
env:
RUNNER_TOOL_CACHE: /toolcache
runs-on: ubuntu-latest
permissions:

View File

@@ -86,7 +86,8 @@ The weather integration is optional, and will be enabled automatically if you pr
The uptime integration is optional, and will be enabled automatically if you provide an API key. The following only applies if you are using the UptimeRobot integration.
| Environment Variable | Description | Required | Default |
| ------------------------- | ------------------------------------------------- | -------- | ------- |
| ------------------------- | ------------------------------------------------------------------ | -------- | ----------- |
| `UPTIME_PROVIDER` | The uptime provider to use, either `uptimerobot` or `betteruptime` | false | uptimerobot |
| `PASSPORT_UPTIME_API_KEY` | The UptimeRobot API key | true | |
| `UPTIME_UPDATE_INTERVAL` | The interval in seconds to update the uptime data | false | 300 |

View File

@@ -6,6 +6,7 @@ import (
"io"
"log"
"net/http"
"sort"
"sync"
"time"
)
@@ -16,19 +17,20 @@ type DepricatedUptimeConfig struct {
}
type UptimeConfig struct {
Provider string `env:"UPTIME_PROVIDER" envDefault:"uptimerobot"`
APIKey string
UpdateInterval int `env:"UPTIME_UPDATE_INTERVAL" envDefault:"300"`
}
type UptimeRobotSite struct {
FriendlyName string `json:"friendly_name"`
Url string `json:"url"`
Status int `json:"status"`
Up bool `json:"-"`
type UptimeSite struct {
FriendlyName string
Url string
Up bool
}
type UptimeManager struct {
sites []UptimeRobotSite
provider string
sites []UptimeSite
lastUpdate time.Time
mutex sync.RWMutex
updateChan chan struct{}
@@ -38,7 +40,7 @@ type UptimeManager struct {
func NewUptimeManager(config *UptimeConfig) *UptimeManager {
if config.APIKey == "" {
log.Fatalln("UptimeRobot API Key is required!")
log.Fatalln("An API Key is required to use Uptime Monitoring!")
return nil
}
@@ -48,10 +50,11 @@ func NewUptimeManager(config *UptimeConfig) *UptimeManager {
}
uptimeManager := &UptimeManager{
provider: config.Provider,
updateChan: make(chan struct{}),
updateInterval: updateInterval,
apiKey: config.APIKey,
sites: []UptimeRobotSite{},
sites: []UptimeSite{},
}
go uptimeManager.updateWorker()
@@ -61,7 +64,7 @@ func NewUptimeManager(config *UptimeConfig) *UptimeManager {
return uptimeManager
}
func (u *UptimeManager) GetUptime() []UptimeRobotSite {
func (u *UptimeManager) GetUptime() []UptimeSite {
u.mutex.RLock()
defer u.mutex.RUnlock()
return u.sites
@@ -81,36 +84,119 @@ func (u *UptimeManager) updateWorker() {
}
}
type UptimeRobotSite struct {
FriendlyName string `json:"friendly_name"`
Url string `json:"url"`
Status int `json:"status"`
}
type UptimeRobotResponse struct {
Monitors []UptimeRobotSite `json:"monitors"`
}
type BetterUptimeSite struct {
MonitorType string `json:"type"`
Attributes struct {
PronounceableName string `json:"pronounceable_name"`
Url string `json:"url"`
Status string `json:"status"`
} `json:"attributes"`
}
type BetterUptimeResponse struct {
Monitors []BetterUptimeSite `json:"data"`
}
func (u *UptimeManager) update() {
var monitors []UptimeSite
switch u.provider {
case "uptimerobot":
monitors = u.updateUptimeRobot()
case "betteruptime":
monitors = u.updateBetterUptime()
default:
log.Fatalln("Invalid Uptime Provider!")
}
u.mutex.Lock()
u.sites = monitors
u.lastUpdate = time.Now()
u.mutex.Unlock()
}
func (u *UptimeManager) updateUptimeRobot() []UptimeSite {
resp, err := http.Post("https://api.uptimerobot.com/v2/getMonitors?api_key="+u.apiKey, "application/json", nil)
if err != nil {
fmt.Printf("Error fetching uptime data: %v\n", err)
return
return []UptimeSite{}
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
fmt.Printf("Error reading response: %v\n", err)
return
return []UptimeSite{}
}
var monitors UptimeRobotResponse
if err := json.Unmarshal(body, &monitors); err != nil {
var rawMonitors UptimeRobotResponse
if err := json.Unmarshal(body, &rawMonitors); err != nil {
fmt.Printf("Error parsing uptime data: %v\n", err)
return
return []UptimeSite{}
}
for i, monitor := range monitors.Monitors {
monitors.Monitors[i].Up = monitor.Status == 2
var monitors []UptimeSite
for _, rawMonitor := range rawMonitors.Monitors {
monitors = append(monitors, UptimeSite{
FriendlyName: rawMonitor.FriendlyName,
Url: rawMonitor.Url,
Up: rawMonitor.Status == 2,
})
}
u.mutex.Lock()
u.sites = monitors.Monitors
u.lastUpdate = time.Now()
u.mutex.Unlock()
return monitors
}
func (u *UptimeManager) updateBetterUptime() []UptimeSite {
client := &http.Client{}
req, err := http.NewRequest("GET", "https://uptime.betterstack.com/api/v2/monitors", nil)
if err != nil {
fmt.Printf("Error fetching uptime data: %v\n", err)
return []UptimeSite{}
}
req.Header.Add("Authorization", "Bearer "+u.apiKey)
resp, err := client.Do(req)
if err != nil {
fmt.Printf("Error fetching uptime data: %v\n", err)
return []UptimeSite{}
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
fmt.Printf("Error reading response: %v\n", err)
return []UptimeSite{}
}
var rawMonitors BetterUptimeResponse
if err := json.Unmarshal(body, &rawMonitors); err != nil {
fmt.Printf("Error parsing uptime data: %v\n", err)
return []UptimeSite{}
}
// alphabetically sort the monitors because UptimeRobot does, but BetterUptime doesnt (or sorts by something else?), and I want them to be consistent
sort.Slice(rawMonitors.Monitors, func(i, j int) bool {
return rawMonitors.Monitors[i].Attributes.PronounceableName < rawMonitors.Monitors[j].Attributes.PronounceableName
})
var monitors []UptimeSite
for _, rawMonitor := range rawMonitors.Monitors {
monitors = append(monitors, UptimeSite{
FriendlyName: rawMonitor.Attributes.PronounceableName,
Url: rawMonitor.Attributes.Url,
Up: rawMonitor.Attributes.Status == "up",
})
}
return monitors
}

View File

@@ -18,7 +18,7 @@
"build:arm64": "zqdgr generate && GOOS=linux GOARCH=arm64 go build -tags netgo,prod -ldflags=\"-w -s\" -o passport-linux-arm64 src/main.go && upx passport-linux-arm64"
},
"pattern": "src/**/*.{go,hbs,css,js,svg,png,jpg,jpeg,webp,woff2,ico,webp}",
"excluded_files": [
"excluded_globs": [
"src/assets/styles"
],
"shutdown_signal": "SIGINT"