diff --git a/assets/status-down.svg b/assets/status-down.svg new file mode 100644 index 0000000..94c37a4 --- /dev/null +++ b/assets/status-down.svg @@ -0,0 +1 @@ +status: downstatusdown \ No newline at end of file diff --git a/assets/status-unknown.svg b/assets/status-unknown.svg new file mode 100644 index 0000000..6da6a26 --- /dev/null +++ b/assets/status-unknown.svg @@ -0,0 +1 @@ +status: unknownstatusunknown \ No newline at end of file diff --git a/assets/status-up.svg b/assets/status-up.svg new file mode 100644 index 0000000..b3c4b11 --- /dev/null +++ b/assets/status-up.svg @@ -0,0 +1 @@ +status: upstatusup \ No newline at end of file diff --git a/config.go b/config.go index 30cbbea..1b17844 100644 --- a/config.go +++ b/config.go @@ -14,6 +14,8 @@ import ( ) func reloadConfig() { + log.Info("Loading configuration...") + err := viper.ReadInConfig() // Find and read the config file if err != nil { // Handle errors reading the config file @@ -55,7 +57,15 @@ func reloadConfig() { mirrors["default"] = append(mirrors["NA"], mirrors["EU"]...) - mirrorMap = mirrors + regionMap = mirrors + + hosts := make(map[string]*Server) + + for _, server := range servers { + hosts[server.Host] = server + } + + hostMap = hosts // Check top choices size if topChoices > len(servers) { diff --git a/http.go b/http.go index b9db356..f8df4f4 100644 --- a/http.go +++ b/http.go @@ -17,29 +17,6 @@ func statusHandler(w http.ResponseWriter, r *http.Request) { w.Write([]byte("OK")) } -func legacyMirrorsHandler(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - - mirrorOutput := make(map[string][]string) - - for region, mirrors := range mirrorMap { - list := make([]string, len(mirrors)) - - for i, mirror := range mirrors { - list[i] = r.URL.Scheme + "://" + mirror.Host + "/" + strings.TrimLeft(mirror.Path, "/") - } - - mirrorOutput[region] = list - } - - json.NewEncoder(w).Encode(mirrorOutput) -} - -func mirrorsHandler(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(servers) -} - func redirectHandler(w http.ResponseWriter, r *http.Request) { ipStr, _, err := net.SplitHostPort(r.RemoteAddr) @@ -67,7 +44,7 @@ func redirectHandler(w http.ResponseWriter, r *http.Request) { parts := strings.Split(r.URL.Path, "/") // region = parts[2] - if mirrors, ok := mirrorMap[parts[2]]; ok { + if mirrors, ok := regionMap[parts[2]]; ok { choices := make([]randutil.Choice, len(mirrors)) for i, item := range mirrors { diff --git a/main.go b/main.go index 7dad8f2..42f9910 100644 --- a/main.go +++ b/main.go @@ -18,11 +18,12 @@ import ( ) var ( - db *maxminddb.Reader - servers ServerList - mirrorMap map[string][]*Server - - dlMap map[string]string + db *maxminddb.Reader + servers ServerList + regionMap map[string][]*Server + hostMap map[string]*Server + dlMap map[string]string + topChoices int redirectsServed = promauto.NewCounter(prometheus.CounterOpts{ Name: "armbian_router_redirects", @@ -35,8 +36,6 @@ var ( }) serverCache *lru.Cache - - topChoices int ) type LocationLookup struct { @@ -114,6 +113,7 @@ func main() { r.Head("/status", statusHandler) r.Get("/status", statusHandler) r.Get("/mirrors", legacyMirrorsHandler) + r.Get("/mirrors/{server}.svg", mirrorStatusHandler) r.Get("/mirrors.json", mirrorsHandler) r.Post("/reload", reloadHandler) r.Get("/dl_map", dlMapHandler) diff --git a/mirrors.go b/mirrors.go new file mode 100644 index 0000000..ed92411 --- /dev/null +++ b/mirrors.go @@ -0,0 +1,69 @@ +package main + +import ( + _ "embed" + "encoding/json" + "github.com/go-chi/chi/v5" + "net/http" + "strings" +) + +func legacyMirrorsHandler(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + + mirrorOutput := make(map[string][]string) + + for region, mirrors := range regionMap { + list := make([]string, len(mirrors)) + + for i, mirror := range mirrors { + list[i] = r.URL.Scheme + "://" + mirror.Host + "/" + strings.TrimLeft(mirror.Path, "/") + } + + mirrorOutput[region] = list + } + + json.NewEncoder(w).Encode(mirrorOutput) +} + +func mirrorsHandler(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(servers) +} + +var ( + //go:embed assets/status-up.svg + statusUp []byte + + //go:embed assets/status-down.svg + statusDown []byte + + //go:embed assets/status-unknown.svg + statusUnknown []byte +) + +func mirrorStatusHandler(w http.ResponseWriter, r *http.Request) { + serverHost := chi.URLParam(r, "server") + + w.Header().Set("Content-Type", "image/svg+xml;charset=utf-8") + + if serverHost == "" { + w.Write(statusUnknown) + return + } + + serverHost = strings.Replace(serverHost, "_", ".", -1) + + server, ok := hostMap[serverHost] + + if !ok { + w.Write(statusUnknown) + return + } + + if server.Available { + w.Write(statusUp) + } else { + w.Write(statusDown) + } +} diff --git a/servers.go b/servers.go index 5bca3aa..b76553f 100644 --- a/servers.go +++ b/servers.go @@ -16,19 +16,20 @@ import ( var ( checkClient = &http.Client{ - Timeout: 10 * time.Second, + Timeout: 20 * time.Second, } ) type Server struct { - Available bool `json:"available"` - Host string `json:"host"` - Path string `json:"path"` - Latitude float64 `json:"latitude"` - Longitude float64 `json:"longitude"` - Weight int `json:"weight"` - Continent string `json:"continent"` - Redirects prometheus.Counter `json:"-"` + Available bool `json:"available"` + Host string `json:"host"` + Path string `json:"path"` + Latitude float64 `json:"latitude"` + Longitude float64 `json:"longitude"` + Weight int `json:"weight"` + Continent string `json:"continent"` + Redirects prometheus.Counter `json:"-"` + LastChange time.Time `json:"lastChange"` } func (server *Server) checkStatus() { @@ -55,6 +56,7 @@ func (server *Server) checkStatus() { }).Info("Server went offline") server.Available = false + server.LastChange = time.Now() } else { log.WithFields(log.Fields{ "server": server.Host, @@ -72,6 +74,7 @@ func (server *Server) checkStatus() { if res.StatusCode == http.StatusOK || res.StatusCode == http.StatusMovedPermanently || res.StatusCode == http.StatusFound || res.StatusCode == http.StatusNotFound { if !server.Available { server.Available = true + server.LastChange = time.Now() log.WithFields(responseFields).Info("Server is online") } } else { @@ -80,6 +83,7 @@ func (server *Server) checkStatus() { if server.Available { log.WithFields(responseFields).Info("Server went offline") server.Available = false + server.LastChange = time.Now() } } }