Compare commits
5 Commits
Author | SHA1 | Date | |
---|---|---|---|
2f43f2d934 | |||
a571832239 | |||
20ae76ff06 | |||
b4ed1fc1a3 | |||
aa8a187bda |
21
config.go
21
config.go
@ -46,6 +46,17 @@ func reloadConfig() {
|
|||||||
// Reload server list
|
// Reload server list
|
||||||
reloadServers()
|
reloadServers()
|
||||||
|
|
||||||
|
// Create mirror map
|
||||||
|
mirrors := make(map[string][]*Server)
|
||||||
|
|
||||||
|
for _, server := range servers {
|
||||||
|
mirrors[server.Continent] = append(mirrors[server.Continent], server)
|
||||||
|
}
|
||||||
|
|
||||||
|
mirrors["default"] = append(mirrors["NA"], mirrors["EU"]...)
|
||||||
|
|
||||||
|
mirrorMap = mirrors
|
||||||
|
|
||||||
// Check top choices size
|
// Check top choices size
|
||||||
if topChoices > len(servers) {
|
if topChoices > len(servers) {
|
||||||
topChoices = len(servers)
|
topChoices = len(servers)
|
||||||
@ -150,14 +161,15 @@ func addServer(server ServerConfig, u *url.URL) *Server {
|
|||||||
Path: u.Path,
|
Path: u.Path,
|
||||||
Latitude: server.Latitude,
|
Latitude: server.Latitude,
|
||||||
Longitude: server.Longitude,
|
Longitude: server.Longitude,
|
||||||
|
Continent: server.Continent,
|
||||||
Weight: server.Weight,
|
Weight: server.Weight,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Defaults to 10 to allow servers to be set lower for lower priority
|
||||||
if s.Weight == 0 {
|
if s.Weight == 0 {
|
||||||
s.Weight = 1
|
s.Weight = 10
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.Latitude == 0 && s.Longitude == 0 {
|
|
||||||
ips, err := net.LookupIP(u.Host)
|
ips, err := net.LookupIP(u.Host)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -180,6 +192,11 @@ func addServer(server ServerConfig, u *url.URL) *Server {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if s.Continent == "" {
|
||||||
|
s.Continent = city.Continent.Code
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.Latitude == 0 && s.Longitude == 0 {
|
||||||
s.Latitude = city.Location.Latitude
|
s.Latitude = city.Location.Latitude
|
||||||
s.Longitude = city.Location.Longitude
|
s.Longitude = city.Location.Longitude
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
# GeoIP Database Path
|
# GeoIP Database Path
|
||||||
geodb: GeoLite2-City.mmdb
|
geodb: GeoLite2-City.mmdb
|
||||||
|
dl_map: userdata.csv
|
||||||
|
|
||||||
# LRU Cache Size (in items)
|
# LRU Cache Size (in items)
|
||||||
cacheSize: 1024
|
cacheSize: 1024
|
||||||
@ -13,25 +14,26 @@ cacheSize: 1024
|
|||||||
servers:
|
servers:
|
||||||
- server: armbian.12z.eu/apt/
|
- server: armbian.12z.eu/apt/
|
||||||
- server: armbian.chi.auroradev.org/apt/
|
- server: armbian.chi.auroradev.org/apt/
|
||||||
weight: 5
|
weight: 15
|
||||||
latitude: 41.8879
|
latitude: 41.8879
|
||||||
longitude: -88.1995
|
longitude: -88.1995
|
||||||
- server: armbian.hosthatch.com/apt/
|
- server: armbian.hosthatch.com/apt/
|
||||||
- server: armbian.lv.auroradev.org/apt/
|
- server: armbian.lv.auroradev.org/apt/
|
||||||
weight: 5
|
weight: 15
|
||||||
- server: armbian.site-meganet.com/apt/
|
- server: armbian.site-meganet.com/apt/
|
||||||
- server: armbian.systemonachip.net/apt/
|
- server: armbian.systemonachip.net/apt/
|
||||||
- server: armbian.tnahosting.net/apt/
|
- server: armbian.tnahosting.net/apt/
|
||||||
weight: 5
|
weight: 15
|
||||||
- server: au-mirror.bret.dk/armbian/apt/
|
- server: au-mirror.bret.dk/armbian/apt/
|
||||||
- server: es-mirror.bret.dk/armbian/apt/
|
- server: es-mirror.bret.dk/armbian/apt/
|
||||||
- server: imola.armbian.com/apt/
|
- server: imola.armbian.com/apt/
|
||||||
- server: mirror.iscas.ac.cn/armbian/
|
- server: mirror.iscas.ac.cn/armbian/
|
||||||
- server: mirror.sjtu.edu.cn/armbian/
|
- server: mirror.sjtu.edu.cn/armbian/
|
||||||
- server: mirrors.aliyun.com/armbian/
|
- server: mirrors.aliyun.com/armbian/
|
||||||
|
continent: AS
|
||||||
- server: mirrors.bfsu.edu.cn/armbian/
|
- server: mirrors.bfsu.edu.cn/armbian/
|
||||||
- server: mirrors.dotsrc.org/armbian-apt/
|
- server: mirrors.dotsrc.org/armbian-apt/
|
||||||
weight: 5
|
weight: 15
|
||||||
- server: mirrors.netix.net/armbian/apt/
|
- server: mirrors.netix.net/armbian/apt/
|
||||||
- server: mirrors.nju.edu.cn/armbian/
|
- server: mirrors.nju.edu.cn/armbian/
|
||||||
- server: mirrors.sustech.edu.cn/armbian/
|
- server: mirrors.sustech.edu.cn/armbian/
|
||||||
@ -41,3 +43,5 @@ servers:
|
|||||||
- server: sg-mirror.bret.dk/armbian/apt/
|
- server: sg-mirror.bret.dk/armbian/apt/
|
||||||
- server: stpete-mirror.armbian.com/apt/
|
- server: stpete-mirror.armbian.com/apt/
|
||||||
- server: xogium.performanceservers.nl/apt/
|
- server: xogium.performanceservers.nl/apt/
|
||||||
|
- server: github.com/armbian/mirror/releases/download/
|
||||||
|
continent: GITHUB
|
102
http.go
102
http.go
@ -3,20 +3,40 @@ package main
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/jmcvetta/randutil"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func statusHandler(w http.ResponseWriter, r *http.Request) {
|
func statusHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
|
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) {
|
func mirrorsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
|
||||||
json.NewEncoder(w).Encode(servers)
|
json.NewEncoder(w).Encode(servers)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,18 +50,59 @@ func redirectHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
ip := net.ParseIP(ipStr)
|
ip := net.ParseIP(ipStr)
|
||||||
|
|
||||||
// TODO: This is temporary to allow testing on private addresses.
|
if ip.IsLoopback() || ip.IsPrivate() {
|
||||||
if ip.IsPrivate() {
|
overrideIP := os.Getenv("OVERRIDE_IP")
|
||||||
ip = net.ParseIP("1.1.1.1")
|
|
||||||
|
if overrideIP == "" {
|
||||||
|
overrideIP = "1.1.1.1"
|
||||||
}
|
}
|
||||||
|
|
||||||
server, distance, err := servers.Closest(ip)
|
ip = net.ParseIP(overrideIP)
|
||||||
|
}
|
||||||
|
|
||||||
|
var server *Server
|
||||||
|
var distance float64
|
||||||
|
|
||||||
|
if strings.HasPrefix(r.URL.Path, "/region") {
|
||||||
|
parts := strings.Split(r.URL.Path, "/")
|
||||||
|
|
||||||
|
// region = parts[2]
|
||||||
|
if mirrors, ok := mirrorMap[parts[2]]; ok {
|
||||||
|
choices := make([]randutil.Choice, len(mirrors))
|
||||||
|
|
||||||
|
for i, item := range mirrors {
|
||||||
|
if !item.Available {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
choices[i] = randutil.Choice{
|
||||||
|
Weight: item.Weight,
|
||||||
|
Item: item,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
choice, err := randutil.WeightedChoice(choices)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
server = choice.Item.(*Server)
|
||||||
|
|
||||||
|
r.URL.Path = strings.Join(parts[3:], "/")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if server == nil {
|
||||||
|
server, distance, err = servers.Closest(ip)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
scheme := r.URL.Scheme
|
scheme := r.URL.Scheme
|
||||||
|
|
||||||
if scheme == "" {
|
if scheme == "" {
|
||||||
@ -57,6 +118,10 @@ func redirectHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if strings.HasSuffix(r.URL.Path, "/") && !strings.HasSuffix(redirectPath, "/") {
|
||||||
|
redirectPath += "/"
|
||||||
|
}
|
||||||
|
|
||||||
u := &url.URL{
|
u := &url.URL{
|
||||||
Scheme: scheme,
|
Scheme: scheme,
|
||||||
Host: server.Host,
|
Host: server.Host,
|
||||||
@ -66,13 +131,16 @@ func redirectHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
server.Redirects.Inc()
|
server.Redirects.Inc()
|
||||||
redirectsServed.Inc()
|
redirectsServed.Inc()
|
||||||
|
|
||||||
|
if distance > 0 {
|
||||||
w.Header().Set("X-Geo-Distance", fmt.Sprintf("%f", distance))
|
w.Header().Set("X-Geo-Distance", fmt.Sprintf("%f", distance))
|
||||||
|
}
|
||||||
|
|
||||||
w.Header().Set("Location", u.String())
|
w.Header().Set("Location", u.String())
|
||||||
w.WriteHeader(http.StatusFound)
|
w.WriteHeader(http.StatusFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
func reloadHandler(w http.ResponseWriter, r *http.Request) {
|
func reloadHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
reloadMap()
|
reloadConfig()
|
||||||
|
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
w.Write([]byte("OK"))
|
w.Write([]byte("OK"))
|
||||||
@ -88,3 +156,25 @@ func dlMapHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
json.NewEncoder(w).Encode(dlMap)
|
json.NewEncoder(w).Encode(dlMap)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func geoIPHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ipStr, _, err := net.SplitHostPort(r.RemoteAddr)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ip := net.ParseIP(ipStr)
|
||||||
|
|
||||||
|
var city City
|
||||||
|
err = db.Lookup(ip, &city)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
|
json.NewEncoder(w).Encode(city)
|
||||||
|
}
|
||||||
|
34
main.go
34
main.go
@ -20,6 +20,7 @@ import (
|
|||||||
var (
|
var (
|
||||||
db *maxminddb.Reader
|
db *maxminddb.Reader
|
||||||
servers ServerList
|
servers ServerList
|
||||||
|
mirrorMap map[string][]*Server
|
||||||
|
|
||||||
dlMap map[string]string
|
dlMap map[string]string
|
||||||
|
|
||||||
@ -38,18 +39,42 @@ var (
|
|||||||
topChoices int
|
topChoices int
|
||||||
)
|
)
|
||||||
|
|
||||||
// City represents a MaxmindDB city
|
type LocationLookup struct {
|
||||||
type City struct {
|
|
||||||
Location struct {
|
Location struct {
|
||||||
Latitude float64 `maxminddb:"latitude"`
|
Latitude float64 `maxminddb:"latitude"`
|
||||||
Longitude float64 `maxminddb:"longitude"`
|
Longitude float64 `maxminddb:"longitude"`
|
||||||
} `maxminddb:"location"`
|
} `maxminddb:"location"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// City represents a MaxmindDB city
|
||||||
|
type City struct {
|
||||||
|
Continent struct {
|
||||||
|
Code string `maxminddb:"code" json:"code"`
|
||||||
|
GeoNameID uint `maxminddb:"geoname_id" json:"geoname_id"`
|
||||||
|
Names map[string]string `maxminddb:"names" json:"names"`
|
||||||
|
} `maxminddb:"continent" json:"continent"`
|
||||||
|
Country struct {
|
||||||
|
GeoNameID uint `maxminddb:"geoname_id" json:"geoname_id"`
|
||||||
|
IsoCode string `maxminddb:"iso_code" json:"iso_code"`
|
||||||
|
Names map[string]string `maxminddb:"names" json:"names"`
|
||||||
|
} `maxminddb:"country" json:"country"`
|
||||||
|
Location struct {
|
||||||
|
AccuracyRadius uint16 `maxminddb:"accuracy_radius" json:'accuracy_radius'`
|
||||||
|
Latitude float64 `maxminddb:"latitude" json:"latitude"`
|
||||||
|
Longitude float64 `maxminddb:"longitude" json:"longitude"`
|
||||||
|
} `maxminddb:"location"`
|
||||||
|
RegisteredCountry struct {
|
||||||
|
GeoNameID uint `maxminddb:"geoname_id" json:"geoname_id"`
|
||||||
|
IsoCode string `maxminddb:"iso_code" json:"iso_code"`
|
||||||
|
Names map[string]string `maxminddb:"names" json:"names"`
|
||||||
|
} `maxminddb:"registered_country" json:"registered_country"`
|
||||||
|
}
|
||||||
|
|
||||||
type ServerConfig struct {
|
type ServerConfig struct {
|
||||||
Server string `mapstructure:"server" yaml:"server"`
|
Server string `mapstructure:"server" yaml:"server"`
|
||||||
Latitude float64 `mapstructure:"latitude" yaml:"latitude"`
|
Latitude float64 `mapstructure:"latitude" yaml:"latitude"`
|
||||||
Longitude float64 `mapstructure:"longitude" yaml:"longitude"`
|
Longitude float64 `mapstructure:"longitude" yaml:"longitude"`
|
||||||
|
Continent string `mapstructure:"continent"`
|
||||||
Weight int `mapstructure:"weight" yaml:"weight"`
|
Weight int `mapstructure:"weight" yaml:"weight"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,10 +111,13 @@ func main() {
|
|||||||
r.Use(RealIPMiddleware)
|
r.Use(RealIPMiddleware)
|
||||||
r.Use(logger.Logger("router", log.StandardLogger()))
|
r.Use(logger.Logger("router", log.StandardLogger()))
|
||||||
|
|
||||||
|
r.Head("/status", statusHandler)
|
||||||
r.Get("/status", statusHandler)
|
r.Get("/status", statusHandler)
|
||||||
r.Get("/mirrors", mirrorsHandler)
|
r.Get("/mirrors", legacyMirrorsHandler)
|
||||||
|
r.Get("/mirrors.json", mirrorsHandler)
|
||||||
r.Post("/reload", reloadHandler)
|
r.Post("/reload", reloadHandler)
|
||||||
r.Get("/dl_map", dlMapHandler)
|
r.Get("/dl_map", dlMapHandler)
|
||||||
|
r.Get("/geoip", geoIPHandler)
|
||||||
r.Get("/metrics", promhttp.Handler().ServeHTTP)
|
r.Get("/metrics", promhttp.Handler().ServeHTTP)
|
||||||
|
|
||||||
r.NotFound(redirectHandler)
|
r.NotFound(redirectHandler)
|
||||||
|
65
servers.go
65
servers.go
@ -21,13 +21,14 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
Available bool
|
Available bool `json:"available"`
|
||||||
Host string
|
Host string `json:"host"`
|
||||||
Path string
|
Path string `json:"path"`
|
||||||
Latitude float64
|
Latitude float64 `json:"latitude"`
|
||||||
Longitude float64
|
Longitude float64 `json:"longitude"`
|
||||||
Weight int
|
Weight int `json:"weight"`
|
||||||
Redirects prometheus.Counter
|
Continent string `json:"continent"`
|
||||||
|
Redirects prometheus.Counter `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (server *Server) checkStatus() {
|
func (server *Server) checkStatus() {
|
||||||
@ -122,30 +123,14 @@ type ComputedDistance struct {
|
|||||||
// DistanceList is a list of Computed Distances with an easy "Choices" func
|
// DistanceList is a list of Computed Distances with an easy "Choices" func
|
||||||
type DistanceList []ComputedDistance
|
type DistanceList []ComputedDistance
|
||||||
|
|
||||||
func (d DistanceList) Choices() []randutil.Choice {
|
|
||||||
c := make([]randutil.Choice, len(d))
|
|
||||||
|
|
||||||
for i, item := range d {
|
|
||||||
c[i] = randutil.Choice{
|
|
||||||
Weight: item.Server.Weight,
|
|
||||||
Item: item,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
// Closest will use GeoIP on the IP provided and find the closest servers.
|
// Closest will use GeoIP on the IP provided and find the closest servers.
|
||||||
// When we have a list of x servers closest, we can choose a random or weighted one.
|
// When we have a list of x servers closest, we can choose a random or weighted one.
|
||||||
// Return values are the closest server, the distance, and if an error occurred.
|
// Return values are the closest server, the distance, and if an error occurred.
|
||||||
func (s ServerList) Closest(ip net.IP) (*Server, float64, error) {
|
func (s ServerList) Closest(ip net.IP) (*Server, float64, error) {
|
||||||
i, exists := serverCache.Get(ip.String())
|
choiceInterface, exists := serverCache.Get(ip.String())
|
||||||
|
|
||||||
if exists {
|
if !exists {
|
||||||
return i.(ComputedDistance).Server, i.(ComputedDistance).Distance, nil
|
var city LocationLookup
|
||||||
}
|
|
||||||
|
|
||||||
var city City
|
|
||||||
err := db.Lookup(ip, &city)
|
err := db.Lookup(ip, &city)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -159,18 +144,38 @@ func (s ServerList) Closest(ip net.IP) (*Server, float64, error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
distance := Distance(city.Location.Latitude, city.Location.Longitude, server.Latitude, server.Longitude)
|
||||||
|
|
||||||
c[i] = ComputedDistance{
|
c[i] = ComputedDistance{
|
||||||
Server: server,
|
Server: server,
|
||||||
Distance: Distance(city.Location.Latitude, city.Location.Longitude, server.Latitude, server.Longitude),
|
Distance: distance,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort by distance
|
// Sort by distance
|
||||||
sort.Slice(s, func(i int, j int) bool {
|
sort.Slice(c, func(i int, j int) bool {
|
||||||
return c[i].Distance < c[j].Distance
|
return c[i].Distance < c[j].Distance
|
||||||
})
|
})
|
||||||
|
|
||||||
choice, err := randutil.WeightedChoice(c[0:topChoices].Choices())
|
choices := make([]randutil.Choice, topChoices)
|
||||||
|
|
||||||
|
for i, item := range c[0:topChoices] {
|
||||||
|
if item.Server == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
choices[i] = randutil.Choice{
|
||||||
|
Weight: item.Server.Weight,
|
||||||
|
Item: item,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
choiceInterface = choices
|
||||||
|
|
||||||
|
serverCache.Add(ip.String(), choiceInterface)
|
||||||
|
}
|
||||||
|
|
||||||
|
choice, err := randutil.WeightedChoice(choiceInterface.([]randutil.Choice))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, -1, err
|
return nil, -1, err
|
||||||
@ -178,8 +183,6 @@ func (s ServerList) Closest(ip net.IP) (*Server, float64, error) {
|
|||||||
|
|
||||||
dist := choice.Item.(ComputedDistance)
|
dist := choice.Item.(ComputedDistance)
|
||||||
|
|
||||||
serverCache.Add(ip.String(), dist)
|
|
||||||
|
|
||||||
return dist.Server, dist.Distance, nil
|
return dist.Server, dist.Distance, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user