Compare commits
No commits in common. "master" and "v0.0.3" have entirely different histories.
18
.drone.yml
18
.drone.yml
|
@ -3,18 +3,6 @@ name: default
|
||||||
type: docker
|
type: docker
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: test
|
|
||||||
image: golang:alpine
|
|
||||||
volumes:
|
|
||||||
- name: build
|
|
||||||
path: /build
|
|
||||||
commands:
|
|
||||||
- go mod download
|
|
||||||
- go install github.com/onsi/ginkgo/v2/ginkgo
|
|
||||||
- ginkgo --randomize-all --p --cover --coverprofile=cover.out .
|
|
||||||
- go tool cover -func=cover.out
|
|
||||||
environment:
|
|
||||||
CGO_ENABLED: '0'
|
|
||||||
- name: build
|
- name: build
|
||||||
image: tystuyfzand/goc:latest
|
image: tystuyfzand/goc:latest
|
||||||
volumes:
|
volumes:
|
||||||
|
@ -28,7 +16,7 @@ steps:
|
||||||
environment:
|
environment:
|
||||||
GOOS: linux,windows,darwin
|
GOOS: linux,windows,darwin
|
||||||
GOARCH: 386,amd64,arm,arm64
|
GOARCH: 386,amd64,arm,arm64
|
||||||
depends_on: [ test ]
|
depends_on: [ clone ]
|
||||||
- name: release
|
- name: release
|
||||||
image: plugins/gitea-release
|
image: plugins/gitea-release
|
||||||
volumes:
|
volumes:
|
||||||
|
@ -60,9 +48,7 @@ steps:
|
||||||
from_secret: docker_password
|
from_secret: docker_password
|
||||||
repo: registry.meow.tf/tyler/armbian-router
|
repo: registry.meow.tf/tyler/armbian-router
|
||||||
registry: registry.meow.tf
|
registry: registry.meow.tf
|
||||||
depends_on: [ test ]
|
depends_on: [ clone ]
|
||||||
when:
|
|
||||||
event: tag
|
|
||||||
volumes:
|
volumes:
|
||||||
- name: build
|
- name: build
|
||||||
temp: {}
|
temp: {}
|
|
@ -2,5 +2,4 @@
|
||||||
userdata.csv
|
userdata.csv
|
||||||
dlrouter-apt.yaml
|
dlrouter-apt.yaml
|
||||||
*.yaml
|
*.yaml
|
||||||
!dlrouter.yaml
|
!dlrouter.yaml
|
||||||
*.exe
|
|
2
LICENSE
2
LICENSE
|
@ -1,4 +1,4 @@
|
||||||
Copyright (c) 2022 Tyler Stuyfzand <admin@meow.tf>, Armbian Project
|
Copyright (c) 2022 Tyler Stuyfzand <admin@meow.tf>
|
||||||
|
|
||||||
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
|
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
|
||||||
|
|
||||||
|
|
118
README.md
118
README.md
|
@ -1,118 +0,0 @@
|
||||||
Armbian Redirector
|
|
||||||
==================
|
|
||||||
|
|
||||||
This repository contains a redirect service for Armbian downloads, apt, etc.
|
|
||||||
|
|
||||||
It uses multiple current technologies and best practices, including:
|
|
||||||
|
|
||||||
- Go 1.17/1.18
|
|
||||||
- GeoIP + Distance routing
|
|
||||||
- Server weighting, pooling (top x servers are served instead of a single one)
|
|
||||||
- Health checks (HTTP, TLS)
|
|
||||||
|
|
||||||
Code Quality
|
|
||||||
------------
|
|
||||||
|
|
||||||
The code quality isn't the greatest/top tier. All code lives in the "main" package and should be moved at some point.
|
|
||||||
|
|
||||||
Regardless, it is meant to be simple and easy to understand.
|
|
||||||
|
|
||||||
Configuration
|
|
||||||
-------------
|
|
||||||
|
|
||||||
### Modes
|
|
||||||
|
|
||||||
#### Redirect
|
|
||||||
|
|
||||||
Standard redirect functionality
|
|
||||||
|
|
||||||
#### Download Mapping
|
|
||||||
|
|
||||||
Uses the `dl_map` configuration variable to enable mapping of paths to new paths.
|
|
||||||
|
|
||||||
Think symlinks, but in a generated file.
|
|
||||||
|
|
||||||
### Mirrors
|
|
||||||
Mirror targets with trailing slash are placed in the yaml configuration file.
|
|
||||||
|
|
||||||
### Example YAML
|
|
||||||
```yaml
|
|
||||||
# GeoIP Database Path
|
|
||||||
geodb: GeoLite2-City.mmdb
|
|
||||||
|
|
||||||
# Comment out to disable
|
|
||||||
dl_map: userdata.csv
|
|
||||||
|
|
||||||
# LRU Cache Size (in items)
|
|
||||||
cacheSize: 1024
|
|
||||||
|
|
||||||
# Server definition
|
|
||||||
# Weights are just like nginx, where if it's > 1 it'll be chosen x out of x + total times
|
|
||||||
# By default, the top 3 servers are used for choosing the best.
|
|
||||||
# server = full url or host+path
|
|
||||||
# weight = int
|
|
||||||
# optional: latitude, longitude (float)
|
|
||||||
servers:
|
|
||||||
- server: armbian.12z.eu/apt/
|
|
||||||
- server: armbian.chi.auroradev.org/apt/
|
|
||||||
weight: 15
|
|
||||||
latitude: 41.8879
|
|
||||||
longitude: -88.1995
|
|
||||||
````
|
|
||||||
|
|
||||||
## API
|
|
||||||
|
|
||||||
`/status`
|
|
||||||
|
|
||||||
Meant for a simple health check (nginx/etc can 502 or similar if down)
|
|
||||||
|
|
||||||
`/reload`
|
|
||||||
|
|
||||||
Flushes cache and reloads configuration and mapping. Requires reloadToken to be set in the configuration, and a matching token provided in `Authorization: Bearer TOKEN`
|
|
||||||
|
|
||||||
`/mirrors`
|
|
||||||
|
|
||||||
Shows all mirrors in the legacy (by region) format
|
|
||||||
|
|
||||||
`/mirrors.json`
|
|
||||||
|
|
||||||
Shows all mirrors in the new JSON format. Example:
|
|
||||||
|
|
||||||
```json
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"available":true,
|
|
||||||
"host":"imola.armbian.com",
|
|
||||||
"path":"/apt/",
|
|
||||||
"latitude":46.0503,
|
|
||||||
"longitude":14.5046,
|
|
||||||
"weight":10,
|
|
||||||
"continent":"EU",
|
|
||||||
"lastChange":"2022-08-12T06:52:35.029565986Z"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
`/mirrors/{server}.svg`
|
|
||||||
|
|
||||||
Magic SVG path to show badges based on server status, for use in dynamic mirror lists.
|
|
||||||
|
|
||||||
`/dl_map`
|
|
||||||
|
|
||||||
Shows json-encoded download mappings
|
|
||||||
|
|
||||||
`/geoip`
|
|
||||||
|
|
||||||
Shows GeoIP information for the requester
|
|
||||||
|
|
||||||
`/region/REGIONCODE/PATH`
|
|
||||||
|
|
||||||
Using this magic path will redirect to the desired region:
|
|
||||||
|
|
||||||
* NA - North America
|
|
||||||
* EU - Europe
|
|
||||||
* AS - Asia
|
|
||||||
|
|
||||||
`/metrics`
|
|
||||||
|
|
||||||
Prometheus metrics endpoint. Metrics aren't considered private, thus are exposed to the public.
|
|
|
@ -1,13 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
. "github.com/onsi/ginkgo/v2"
|
|
||||||
. "github.com/onsi/gomega"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestArmbianMirror(t *testing.T) {
|
|
||||||
RegisterFailHandler(Fail)
|
|
||||||
RunSpecs(t, "ArmbianMirror Suite")
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="82" height="20" role="img" aria-label="status: down"><title>status: down</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="82" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="43" height="20" fill="#555"/><rect x="43" width="39" height="20" fill="#e05d44"/><rect width="82" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="225" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="330">status</text><text x="225" y="140" transform="scale(.1)" fill="#fff" textLength="330">status</text><text aria-hidden="true" x="615" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="290">down</text><text x="615" y="140" transform="scale(.1)" fill="#fff" textLength="290">down</text></g></svg>
|
|
Before Width: | Height: | Size: 1.1 KiB |
|
@ -1 +0,0 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="104" height="20" role="img" aria-label="status: unknown"><title>status: unknown</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="104" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="43" height="20" fill="#555"/><rect x="43" width="61" height="20" fill="#9f9f9f"/><rect width="104" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="225" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="330">status</text><text x="225" y="140" transform="scale(.1)" fill="#fff" textLength="330">status</text><text aria-hidden="true" x="725" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="510">unknown</text><text x="725" y="140" transform="scale(.1)" fill="#fff" textLength="510">unknown</text></g></svg>
|
|
Before Width: | Height: | Size: 1.1 KiB |
|
@ -1 +0,0 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="66" height="20" role="img" aria-label="status: up"><title>status: up</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="66" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="43" height="20" fill="#555"/><rect x="43" width="23" height="20" fill="#4c1"/><rect width="66" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="225" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="330">status</text><text x="225" y="140" transform="scale(.1)" fill="#fff" textLength="330">status</text><text aria-hidden="true" x="535" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="130">up</text><text x="535" y="140" transform="scale(.1)" fill="#fff" textLength="130">up</text></g></svg>
|
|
Before Width: | Height: | Size: 1.1 KiB |
134
check.go
134
check.go
|
@ -1,134 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/tls"
|
|
||||||
"crypto/x509"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"runtime"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrHttpsRedirect = errors.New("unexpected forced https redirect")
|
|
||||||
ErrCertExpired = errors.New("certificate is expired")
|
|
||||||
)
|
|
||||||
|
|
||||||
// checkHttp checks a URL for validity, and checks redirects
|
|
||||||
func checkHttp(server *Server, logFields log.Fields) (bool, error) {
|
|
||||||
u := &url.URL{
|
|
||||||
Scheme: "http",
|
|
||||||
Host: server.Host,
|
|
||||||
Path: server.Path,
|
|
||||||
}
|
|
||||||
|
|
||||||
req, err := http.NewRequest(http.MethodGet, u.String(), nil)
|
|
||||||
|
|
||||||
req.Header.Set("User-Agent", "ArmbianRouter/1.0 (Go "+runtime.Version()+")")
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := checkClient.Do(req)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
logFields["responseCode"] = res.StatusCode
|
|
||||||
|
|
||||||
if res.StatusCode == http.StatusOK || res.StatusCode == http.StatusMovedPermanently || res.StatusCode == http.StatusFound || res.StatusCode == http.StatusNotFound {
|
|
||||||
if res.StatusCode == http.StatusMovedPermanently || res.StatusCode == http.StatusFound {
|
|
||||||
location := res.Header.Get("Location")
|
|
||||||
|
|
||||||
logFields["url"] = location
|
|
||||||
|
|
||||||
// Check that we don't redirect to https from a http url
|
|
||||||
if u.Scheme == "http" {
|
|
||||||
res, err := checkRedirect(location)
|
|
||||||
|
|
||||||
if !res || err != nil {
|
|
||||||
return res, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
logFields["cause"] = fmt.Sprintf("Unexpected http status %d", res.StatusCode)
|
|
||||||
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// checkRedirect parses a location header response and checks the scheme
|
|
||||||
func checkRedirect(locationHeader string) (bool, error) {
|
|
||||||
newUrl, err := url.Parse(locationHeader)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if newUrl.Scheme == "https" {
|
|
||||||
return false, ErrHttpsRedirect
|
|
||||||
}
|
|
||||||
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// checkTLS checks tls certificates from a host, ensures they're valid, and not expired.
|
|
||||||
func checkTLS(server *Server, logFields log.Fields) (bool, error) {
|
|
||||||
host, port, err := net.SplitHostPort(server.Host)
|
|
||||||
|
|
||||||
if port == "" {
|
|
||||||
port = "443"
|
|
||||||
}
|
|
||||||
|
|
||||||
conn, err := tls.Dial("tcp", host+":"+port, checkTLSConfig)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer conn.Close()
|
|
||||||
|
|
||||||
err = conn.VerifyHostname(server.Host)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
now := time.Now()
|
|
||||||
|
|
||||||
state := conn.ConnectionState()
|
|
||||||
|
|
||||||
opts := x509.VerifyOptions{
|
|
||||||
CurrentTime: time.Now(),
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, cert := range state.PeerCertificates {
|
|
||||||
if _, err := cert.Verify(opts); err != nil {
|
|
||||||
logFields["peerCert"] = cert.Subject.String()
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
if now.Before(cert.NotBefore) || now.After(cert.NotAfter) {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, chain := range state.VerifiedChains {
|
|
||||||
for _, cert := range chain {
|
|
||||||
if now.Before(cert.NotBefore) || now.After(cert.NotAfter) {
|
|
||||||
logFields["cert"] = cert.Subject.String()
|
|
||||||
return false, ErrCertExpired
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true, nil
|
|
||||||
}
|
|
202
check_test.go
202
check_test.go
|
@ -1,202 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/rsa"
|
|
||||||
"crypto/tls"
|
|
||||||
"crypto/x509"
|
|
||||||
"crypto/x509/pkix"
|
|
||||||
"encoding/pem"
|
|
||||||
"fmt"
|
|
||||||
. "github.com/onsi/ginkgo/v2"
|
|
||||||
. "github.com/onsi/gomega"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"math/big"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"net/url"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func genTestCerts(notBefore, notAfter time.Time) (*pem.Block, *pem.Block, error) {
|
|
||||||
// Create a Certificate Authority Cert
|
|
||||||
template := x509.Certificate{
|
|
||||||
SerialNumber: big.NewInt(0),
|
|
||||||
Subject: pkix.Name{CommonName: "localhost"},
|
|
||||||
SignatureAlgorithm: x509.SHA256WithRSA,
|
|
||||||
NotBefore: notBefore,
|
|
||||||
NotAfter: notAfter,
|
|
||||||
BasicConstraintsValid: true,
|
|
||||||
IPAddresses: []net.IP{net.ParseIP("127.0.0.1")},
|
|
||||||
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyAgreement | x509.KeyUsageKeyEncipherment | x509.KeyUsageDataEncipherment,
|
|
||||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a Private Key
|
|
||||||
key, err := rsa.GenerateKey(rand.Reader, 4096)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("Could not generate rsa key - %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use CA Cert to sign a CSR and create a Public Cert
|
|
||||||
csr := &key.PublicKey
|
|
||||||
cert, err := x509.CreateCertificate(rand.Reader, &template, &template, csr, key)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("Could not generate certificate - %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert keys into pem.Block
|
|
||||||
c := &pem.Block{Type: "CERTIFICATE", Bytes: cert}
|
|
||||||
k := &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)}
|
|
||||||
return c, k, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ = Describe("Check suite", func() {
|
|
||||||
var (
|
|
||||||
httpServer *httptest.Server
|
|
||||||
server *Server
|
|
||||||
handler http.HandlerFunc
|
|
||||||
)
|
|
||||||
BeforeEach(func() {
|
|
||||||
httpServer = httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
handler(w, r)
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
AfterEach(func() {
|
|
||||||
httpServer.Close()
|
|
||||||
})
|
|
||||||
setupServer := func() {
|
|
||||||
u, err := url.Parse(httpServer.URL)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
server = &Server{
|
|
||||||
Host: u.Host,
|
|
||||||
Path: u.Path,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Context("HTTP Checks", func() {
|
|
||||||
BeforeEach(func() {
|
|
||||||
httpServer.Start()
|
|
||||||
setupServer()
|
|
||||||
})
|
|
||||||
It("Should successfully check for connectivity", func() {
|
|
||||||
handler = func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := checkHttp(server, log.Fields{})
|
|
||||||
|
|
||||||
Expect(res).To(BeTrue())
|
|
||||||
Expect(err).To(BeNil())
|
|
||||||
})
|
|
||||||
It("Should return an error when redirected to https", func() {
|
|
||||||
handler = func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Header().Set("Location", strings.Replace(httpServer.URL, "http://", "https://", -1))
|
|
||||||
w.WriteHeader(http.StatusMovedPermanently)
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := checkHttp(server, log.Fields{})
|
|
||||||
|
|
||||||
Expect(res).To(BeFalse())
|
|
||||||
Expect(err).To(Equal(ErrHttpsRedirect))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
Context("TLS Checks", func() {
|
|
||||||
var (
|
|
||||||
x509Cert *x509.Certificate
|
|
||||||
)
|
|
||||||
setupCerts := func(notBefore, notAfter time.Time) {
|
|
||||||
cert, key, err := genTestCerts(notBefore, notAfter)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
panic("Unable to generate test certs")
|
|
||||||
}
|
|
||||||
|
|
||||||
x509Cert, err = x509.ParseCertificate(cert.Bytes)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
panic("Unable to parse certificate from bytes: " + err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
tlsPair, err := tls.X509KeyPair(pem.EncodeToMemory(cert), pem.EncodeToMemory(key))
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
panic("Unable to load tls key pair: " + err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
httpServer.TLS = &tls.Config{
|
|
||||||
Certificates: []tls.Certificate{tlsPair},
|
|
||||||
}
|
|
||||||
|
|
||||||
httpServer.StartTLS()
|
|
||||||
setupServer()
|
|
||||||
}
|
|
||||||
Context("CA Tests", func() {
|
|
||||||
BeforeEach(func() {
|
|
||||||
setupCerts(time.Now(), time.Now().Add(24*time.Hour))
|
|
||||||
})
|
|
||||||
It("Should fail due to invalid ca", func() {
|
|
||||||
res, err := checkTLS(server, log.Fields{})
|
|
||||||
|
|
||||||
Expect(res).To(BeFalse())
|
|
||||||
Expect(err).ToNot(BeNil())
|
|
||||||
})
|
|
||||||
It("Should successfully validate certificates (valid ca, valid date/times, etc)", func() {
|
|
||||||
pool := x509.NewCertPool()
|
|
||||||
|
|
||||||
pool.AddCert(x509Cert)
|
|
||||||
|
|
||||||
checkTLSConfig = &tls.Config{RootCAs: pool}
|
|
||||||
|
|
||||||
res, err := checkTLS(server, log.Fields{})
|
|
||||||
|
|
||||||
Expect(res).To(BeFalse())
|
|
||||||
Expect(err).ToNot(BeNil())
|
|
||||||
|
|
||||||
checkTLSConfig = nil
|
|
||||||
})
|
|
||||||
})
|
|
||||||
Context("Expiration tests", func() {
|
|
||||||
AfterEach(func() {
|
|
||||||
checkTLSConfig = nil
|
|
||||||
})
|
|
||||||
It("Should fail due to not yet valid certificate", func() {
|
|
||||||
setupCerts(time.Now().Add(5*time.Hour), time.Now().Add(10*time.Hour))
|
|
||||||
|
|
||||||
// Trust our certs
|
|
||||||
pool := x509.NewCertPool()
|
|
||||||
|
|
||||||
pool.AddCert(x509Cert)
|
|
||||||
|
|
||||||
checkTLSConfig = &tls.Config{RootCAs: pool}
|
|
||||||
|
|
||||||
// Check TLS
|
|
||||||
res, err := checkTLS(server, log.Fields{})
|
|
||||||
|
|
||||||
Expect(res).To(BeFalse())
|
|
||||||
Expect(err).ToNot(BeNil())
|
|
||||||
})
|
|
||||||
It("Should fail due to expired certificate", func() {
|
|
||||||
setupCerts(time.Now().Add(-10*time.Hour), time.Now().Add(-5*time.Hour))
|
|
||||||
|
|
||||||
// Trust our certs
|
|
||||||
pool := x509.NewCertPool()
|
|
||||||
|
|
||||||
pool.AddCert(x509Cert)
|
|
||||||
|
|
||||||
checkTLSConfig = &tls.Config{RootCAs: pool}
|
|
||||||
|
|
||||||
// Check TLS
|
|
||||||
res, err := checkTLS(server, log.Fields{})
|
|
||||||
|
|
||||||
Expect(res).To(BeFalse())
|
|
||||||
Expect(err).ToNot(BeNil())
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
250
config.go
250
config.go
|
@ -1,250 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
lru "github.com/hashicorp/golang-lru"
|
|
||||||
"github.com/oschwald/maxminddb-golang"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
|
||||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
"net"
|
|
||||||
"net/url"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
func reloadConfig() error {
|
|
||||||
log.Info("Loading configuration...")
|
|
||||||
|
|
||||||
err := viper.ReadInConfig() // Find and read the config file
|
|
||||||
|
|
||||||
if err != nil { // Handle errors reading the config file
|
|
||||||
return errors.Wrap(err, "Unable to read configuration")
|
|
||||||
}
|
|
||||||
|
|
||||||
// db will never be reloaded.
|
|
||||||
if db == nil {
|
|
||||||
// Load maxmind database
|
|
||||||
db, err = maxminddb.Open(viper.GetString("geodb"))
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "Unable to open database")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Refresh server cache if size changed
|
|
||||||
if serverCache == nil {
|
|
||||||
serverCache, err = lru.New(viper.GetInt("cacheSize"))
|
|
||||||
} else {
|
|
||||||
serverCache.Resize(viper.GetInt("cacheSize"))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Purge the cache to ensure we don't have any invalid servers in it
|
|
||||||
serverCache.Purge()
|
|
||||||
|
|
||||||
// Set top choice count
|
|
||||||
topChoices = viper.GetInt("topChoices")
|
|
||||||
|
|
||||||
// Reload map file
|
|
||||||
if err := reloadMap(); err != nil {
|
|
||||||
return errors.Wrap(err, "Unable to load map file")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reload server list
|
|
||||||
if err := reloadServers(); err != nil {
|
|
||||||
return errors.Wrap(err, "Unable to load servers")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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"]...)
|
|
||||||
|
|
||||||
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) {
|
|
||||||
topChoices = len(servers)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Force check
|
|
||||||
go servers.Check()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func reloadServers() error {
|
|
||||||
var serverList []ServerConfig
|
|
||||||
|
|
||||||
if err := viper.UnmarshalKey("servers", &serverList); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
|
|
||||||
existing := make(map[string]int)
|
|
||||||
|
|
||||||
for i, server := range servers {
|
|
||||||
existing[server.Host] = i
|
|
||||||
}
|
|
||||||
|
|
||||||
hosts := make(map[string]bool)
|
|
||||||
|
|
||||||
for _, server := range serverList {
|
|
||||||
wg.Add(1)
|
|
||||||
|
|
||||||
var prefix string
|
|
||||||
|
|
||||||
if !strings.HasPrefix(server.Server, "http") {
|
|
||||||
prefix = "https://"
|
|
||||||
}
|
|
||||||
|
|
||||||
u, err := url.Parse(prefix + server.Server)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"error": err,
|
|
||||||
"server": server,
|
|
||||||
}).Warning("Server is invalid")
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
hosts[u.Host] = true
|
|
||||||
|
|
||||||
i := -1
|
|
||||||
|
|
||||||
if v, exists := existing[u.Host]; exists {
|
|
||||||
i = v
|
|
||||||
}
|
|
||||||
|
|
||||||
go func(i int, server ServerConfig, u *url.URL) {
|
|
||||||
defer wg.Done()
|
|
||||||
|
|
||||||
s := addServer(server, u)
|
|
||||||
|
|
||||||
if _, ok := existing[u.Host]; ok {
|
|
||||||
s.Redirects = servers[i].Redirects
|
|
||||||
|
|
||||||
servers[i] = s
|
|
||||||
} else {
|
|
||||||
s.Redirects = promauto.NewCounter(prometheus.CounterOpts{
|
|
||||||
Name: "armbian_router_redirects_" + metricReplacer.Replace(u.Host),
|
|
||||||
Help: "The number of redirects for server " + u.Host,
|
|
||||||
})
|
|
||||||
|
|
||||||
servers = append(servers, s)
|
|
||||||
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"server": u.Host,
|
|
||||||
"path": u.Path,
|
|
||||||
"latitude": s.Latitude,
|
|
||||||
"longitude": s.Longitude,
|
|
||||||
}).Info("Added server")
|
|
||||||
}
|
|
||||||
}(i, server, u)
|
|
||||||
}
|
|
||||||
|
|
||||||
wg.Wait()
|
|
||||||
|
|
||||||
// Remove servers that no longer exist in the config
|
|
||||||
for i := len(servers) - 1; i >= 0; i-- {
|
|
||||||
if _, exists := hosts[servers[i].Host]; exists {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"server": servers[i].Host,
|
|
||||||
}).Info("Removed server")
|
|
||||||
|
|
||||||
servers = append(servers[:i], servers[i+1:]...)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var metricReplacer = strings.NewReplacer(".", "_", "-", "_")
|
|
||||||
|
|
||||||
// addServer takes ServerConfig and constructs a server.
|
|
||||||
// This will create duplicate servers, but it will overwrite existing ones when changed.
|
|
||||||
func addServer(server ServerConfig, u *url.URL) *Server {
|
|
||||||
s := &Server{
|
|
||||||
Available: true,
|
|
||||||
Host: u.Host,
|
|
||||||
Path: u.Path,
|
|
||||||
Latitude: server.Latitude,
|
|
||||||
Longitude: server.Longitude,
|
|
||||||
Continent: server.Continent,
|
|
||||||
Weight: server.Weight,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Defaults to 10 to allow servers to be set lower for lower priority
|
|
||||||
if s.Weight == 0 {
|
|
||||||
s.Weight = 10
|
|
||||||
}
|
|
||||||
|
|
||||||
ips, err := net.LookupIP(u.Host)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"error": err,
|
|
||||||
"server": s.Host,
|
|
||||||
}).Warning("Could not resolve address")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var city City
|
|
||||||
err = db.Lookup(ips[0], &city)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"error": err,
|
|
||||||
"server": s.Host,
|
|
||||||
"ip": ips[0],
|
|
||||||
}).Warning("Could not geolocate address")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.Continent == "" {
|
|
||||||
s.Continent = city.Continent.Code
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.Latitude == 0 && s.Longitude == 0 {
|
|
||||||
s.Latitude = city.Location.Latitude
|
|
||||||
s.Longitude = city.Location.Longitude
|
|
||||||
}
|
|
||||||
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
func reloadMap() error {
|
|
||||||
mapFile := viper.GetString("dl_map")
|
|
||||||
|
|
||||||
if mapFile == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
log.WithField("file", mapFile).Info("Loading download map")
|
|
||||||
|
|
||||||
newMap, err := loadMapFile(mapFile)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
dlMap = newMap
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,47 +1,16 @@
|
||||||
# GeoIP Database Path
|
geodb: GeoIP2-City.mmdb
|
||||||
geodb: GeoLite2-City.mmdb
|
|
||||||
dl_map: userdata.csv
|
|
||||||
|
|
||||||
# LRU Cache Size (in items)
|
|
||||||
cacheSize: 1024
|
|
||||||
|
|
||||||
# Server definition
|
|
||||||
# Weights are just like nginx, where if it's > 1 it'll be chosen x out of x + total times
|
|
||||||
# By default, the top 3 servers are used for choosing the best.
|
|
||||||
# server = full url or host+path
|
|
||||||
# weight = int
|
|
||||||
# optional: latitude, longitude (float)
|
|
||||||
servers:
|
servers:
|
||||||
- server: armbian.12z.eu/apt/
|
- https://mirrors.tuna.tsinghua.edu.cn/armbian-releases/
|
||||||
- server: armbian.chi.auroradev.org/apt/
|
- https://mirrors.bfsu.edu.cn/armbian-releases/
|
||||||
weight: 15
|
- https://mirrors.nju.edu.cn/armbian-releases/
|
||||||
latitude: 41.8879
|
- https://mirrors.ustc.edu.cn/armbian-dl/
|
||||||
longitude: -88.1995
|
- https://mirror.12z.eu/pub/linux/armbian/dl/
|
||||||
- server: armbian.hosthatch.com/apt/
|
- https://armbian.tnahosting.net/dl/
|
||||||
- server: armbian.lv.auroradev.org/apt/
|
- https://stpete-mirror.armbian.com/dl/
|
||||||
weight: 15
|
- https://mirror.armbian.de/dl/
|
||||||
- server: armbian.site-meganet.com/apt/
|
- https://mirrors.netix.net/armbian/dl/
|
||||||
- server: armbian.systemonachip.net/apt/
|
- https://mirrors.dotsrc.org/armbian-dl/
|
||||||
- server: armbian.tnahosting.net/apt/
|
- https://armbian.hosthatch.com/dl/
|
||||||
weight: 15
|
- https://xogium.performanceservers.nl/dl/
|
||||||
- server: au-mirror.bret.dk/armbian/apt/
|
- https://github.com/armbian/mirror/releases/download/
|
||||||
- server: es-mirror.bret.dk/armbian/apt/
|
|
||||||
- server: imola.armbian.com/apt/
|
|
||||||
- server: mirror.iscas.ac.cn/armbian/
|
|
||||||
- server: mirror.sjtu.edu.cn/armbian/
|
|
||||||
- server: mirrors.aliyun.com/armbian/
|
|
||||||
continent: AS
|
|
||||||
- server: mirrors.bfsu.edu.cn/armbian/
|
|
||||||
- server: mirrors.dotsrc.org/armbian-apt/
|
|
||||||
weight: 15
|
|
||||||
- server: mirrors.netix.net/armbian/apt/
|
|
||||||
- server: mirrors.nju.edu.cn/armbian/
|
|
||||||
- server: mirrors.sustech.edu.cn/armbian/
|
|
||||||
- server: mirrors.tuna.tsinghua.edu.cn/armbian/
|
|
||||||
- server: mirrors.ustc.edu.cn/armbian/
|
|
||||||
- server: mirrors.xtom.de/armbian/
|
|
||||||
- server: sg-mirror.bret.dk/armbian/apt/
|
|
||||||
- server: stpete-mirror.armbian.com/apt/
|
|
||||||
- server: xogium.performanceservers.nl/apt/
|
|
||||||
- server: github.com/armbian/mirror/releases/download/
|
|
||||||
continent: GITHUB
|
|
||||||
|
|
12
go.mod
12
go.mod
|
@ -5,12 +5,7 @@ go 1.17
|
||||||
require (
|
require (
|
||||||
github.com/chi-middleware/logrus-logger v0.2.0
|
github.com/chi-middleware/logrus-logger v0.2.0
|
||||||
github.com/go-chi/chi/v5 v5.0.7
|
github.com/go-chi/chi/v5 v5.0.7
|
||||||
github.com/hashicorp/golang-lru v0.5.4
|
|
||||||
github.com/jmcvetta/randutil v0.0.0-20150817122601-2bb1b664bcff
|
|
||||||
github.com/onsi/ginkgo/v2 v2.1.4
|
|
||||||
github.com/onsi/gomega v1.20.0
|
|
||||||
github.com/oschwald/maxminddb-golang v1.8.0
|
github.com/oschwald/maxminddb-golang v1.8.0
|
||||||
github.com/pkg/errors v0.9.1
|
|
||||||
github.com/prometheus/client_golang v1.11.0
|
github.com/prometheus/client_golang v1.11.0
|
||||||
github.com/sirupsen/logrus v1.8.1
|
github.com/sirupsen/logrus v1.8.1
|
||||||
github.com/spf13/viper v1.10.1
|
github.com/spf13/viper v1.10.1
|
||||||
|
@ -21,7 +16,6 @@ require (
|
||||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||||
github.com/fsnotify/fsnotify v1.5.1 // indirect
|
github.com/fsnotify/fsnotify v1.5.1 // indirect
|
||||||
github.com/golang/protobuf v1.5.2 // indirect
|
github.com/golang/protobuf v1.5.2 // indirect
|
||||||
github.com/google/go-cmp v0.5.8 // indirect
|
|
||||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||||
github.com/magiconair/properties v1.8.5 // indirect
|
github.com/magiconair/properties v1.8.5 // indirect
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
||||||
|
@ -35,11 +29,9 @@ require (
|
||||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||||
github.com/spf13/pflag v1.0.5 // indirect
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
github.com/subosito/gotenv v1.2.0 // indirect
|
github.com/subosito/gotenv v1.2.0 // indirect
|
||||||
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 // indirect
|
golang.org/x/sys v0.0.0-20211210111614-af8b64212486 // indirect
|
||||||
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 // indirect
|
|
||||||
golang.org/x/text v0.3.7 // indirect
|
golang.org/x/text v0.3.7 // indirect
|
||||||
google.golang.org/protobuf v1.28.0 // indirect
|
google.golang.org/protobuf v1.27.1 // indirect
|
||||||
gopkg.in/ini.v1 v1.66.2 // indirect
|
gopkg.in/ini.v1 v1.66.2 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
|
||||||
)
|
)
|
||||||
|
|
63
go.sum
63
go.sum
|
@ -110,8 +110,6 @@ github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E
|
||||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||||
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
||||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
|
||||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
|
||||||
github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI=
|
github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI=
|
||||||
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
|
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
|
||||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
|
@ -128,8 +126,6 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9
|
||||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||||
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
||||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
|
|
||||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
|
||||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||||
|
@ -179,9 +175,8 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
|
||||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
|
||||||
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||||
|
@ -198,8 +193,6 @@ github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLe
|
||||||
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||||
github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||||
github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
|
|
||||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
|
||||||
github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||||
github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||||
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||||
|
@ -231,7 +224,6 @@ github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b
|
||||||
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
|
|
||||||
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||||
|
@ -239,12 +231,9 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO
|
||||||
github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc=
|
github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc=
|
||||||
github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
|
github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
|
||||||
github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4=
|
github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4=
|
||||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
|
||||||
github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
|
github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||||
github.com/jmcvetta/randutil v0.0.0-20150817122601-2bb1b664bcff h1:6NvhExg4omUC9NfA+l4Oq3ibNNeJUdiAF3iBVB0PlDk=
|
|
||||||
github.com/jmcvetta/randutil v0.0.0-20150817122601-2bb1b664bcff/go.mod h1:ddfPX8Z28YMjiqoaJhNBzWHapTHXejnB5cDCUWDwriw=
|
|
||||||
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
||||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
|
@ -301,21 +290,6 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb
|
||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
|
||||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
|
||||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
|
||||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
|
||||||
github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
|
|
||||||
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
|
|
||||||
github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
|
|
||||||
github.com/onsi/ginkgo/v2 v2.1.4 h1:GNapqRSid3zijZ9H77KrgVG4/8KqiyRsxcSxe+7ApXY=
|
|
||||||
github.com/onsi/ginkgo/v2 v2.1.4/go.mod h1:um6tUpWM/cxCK3/FK8BXqEiUMUwRgSM4JXG47RKZmLU=
|
|
||||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
|
||||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
|
||||||
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
|
||||||
github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
|
|
||||||
github.com/onsi/gomega v1.20.0 h1:8W0cWlwFkflGPLltQvLRB7ZVD5HuP6ng320w2IS245Q=
|
|
||||||
github.com/onsi/gomega v1.20.0/go.mod h1:DtrZpjmvpn2mPm4YWQa0/ALMDj9v4YxLgojwPeREyVo=
|
|
||||||
github.com/oschwald/maxminddb-golang v1.8.0 h1:Uh/DSnGoxsyp/KYbY1AuP0tYEwfs0sCph9p/UMXK/Hk=
|
github.com/oschwald/maxminddb-golang v1.8.0 h1:Uh/DSnGoxsyp/KYbY1AuP0tYEwfs0sCph9p/UMXK/Hk=
|
||||||
github.com/oschwald/maxminddb-golang v1.8.0/go.mod h1:RXZtst0N6+FY/3qCNmZMBApR19cdQj43/NM9VkrNAis=
|
github.com/oschwald/maxminddb-golang v1.8.0/go.mod h1:RXZtst0N6+FY/3qCNmZMBApR19cdQj43/NM9VkrNAis=
|
||||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||||
|
@ -324,7 +298,6 @@ github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhEC
|
||||||
github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
|
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
@ -392,7 +365,6 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
|
||||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
|
||||||
go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
|
go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
|
||||||
go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
|
go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
|
||||||
go.etcd.io/etcd/client/v2 v2.305.1/go.mod h1:pMEacxZW7o8pg4CrFE7pquyCJJzZvkvdD2RibOCCCGs=
|
go.etcd.io/etcd/client/v2 v2.305.1/go.mod h1:pMEacxZW7o8pg4CrFE7pquyCJJzZvkvdD2RibOCCCGs=
|
||||||
|
@ -416,7 +388,6 @@ golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||||
|
@ -453,10 +424,8 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
|
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
@ -480,7 +449,6 @@ golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/
|
||||||
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
|
||||||
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
|
@ -494,13 +462,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
|
||||||
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
|
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
|
||||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||||
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
|
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
|
||||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
|
||||||
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
|
||||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
|
||||||
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 h1:HVyaeDAYux4pnY+D/SiwmLOR36ewZ4iGQIIrtnuCjFA=
|
|
||||||
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
@ -532,7 +495,6 @@ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJ
|
||||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
@ -544,14 +506,11 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191224085550-c709ea063b76/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191224085550-c709ea063b76/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
@ -578,7 +537,6 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
@ -603,15 +561,10 @@ golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||||
golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20211210111614-af8b64212486 h1:5hpz5aRr+W1erYCL5JRhSUBJRph7l9XkNveoExlrKYk=
|
||||||
golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 h1:xHms4gcpe1YE7A3yIllJXP16CMAGuqwO2lX1mTyyRRc=
|
|
||||||
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
|
||||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
@ -671,7 +624,6 @@ golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82u
|
||||||
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
|
||||||
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||||
|
@ -680,11 +632,10 @@ golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||||
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||||
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||||
golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20=
|
|
||||||
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
|
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||||
|
@ -826,9 +777,8 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj
|
||||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
|
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
|
||||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
|
|
||||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
|
||||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
@ -836,10 +786,8 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
|
||||||
gopkg.in/ini.v1 v1.66.2 h1:XfR1dOYubytKy4Shzc2LHrrGhU0lDCfDGG1yLPmpgsI=
|
gopkg.in/ini.v1 v1.66.2 h1:XfR1dOYubytKy4Shzc2LHrrGhU0lDCfDGG1yLPmpgsI=
|
||||||
gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
|
||||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
@ -850,9 +798,8 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
|
152
http.go
152
http.go
|
@ -3,28 +3,25 @@ package main
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/jmcvetta/randutil"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// statusHandler is a simple handler that will always return 200 OK with a body of "OK"
|
|
||||||
func statusHandler(w http.ResponseWriter, r *http.Request) {
|
func statusHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
|
|
||||||
if r.Method != http.MethodHead {
|
|
||||||
w.Write([]byte("OK"))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// redirectHandler is the default "not found" handler which handles redirects
|
func mirrorsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
// if the environment variable OVERRIDE_IP is set, it will use that ip address
|
w.Header().Set("Content-Type", "application/json")
|
||||||
// this is useful for local testing when you're on the local network
|
|
||||||
|
json.NewEncoder(w).Encode(servers)
|
||||||
|
}
|
||||||
|
|
||||||
func redirectHandler(w http.ResponseWriter, r *http.Request) {
|
func redirectHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ipStr, _, err := net.SplitHostPort(r.RemoteAddr)
|
ipStr, _, err := net.SplitHostPort(r.RemoteAddr)
|
||||||
|
|
||||||
|
@ -35,75 +32,26 @@ func redirectHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
ip := net.ParseIP(ipStr)
|
ip := net.ParseIP(ipStr)
|
||||||
|
|
||||||
if ip.IsLoopback() || ip.IsPrivate() {
|
// TODO: This is temporary to allow testing on private addresses.
|
||||||
overrideIP := os.Getenv("OVERRIDE_IP")
|
if ip.IsPrivate() {
|
||||||
|
ip = net.ParseIP("1.1.1.1")
|
||||||
if overrideIP == "" {
|
|
||||||
overrideIP = "1.1.1.1"
|
|
||||||
}
|
|
||||||
|
|
||||||
ip = net.ParseIP(overrideIP)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var server *Server
|
server, distance, err := servers.Closest(ip)
|
||||||
var distance float64
|
|
||||||
|
|
||||||
// If the path has a prefix of region/NA, it will use specific regions instead
|
if err != nil {
|
||||||
// of the default geographical distance
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
if strings.HasPrefix(r.URL.Path, "/region") {
|
return
|
||||||
parts := strings.Split(r.URL.Path, "/")
|
|
||||||
|
|
||||||
// region = parts[2]
|
|
||||||
if mirrors, ok := regionMap[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 {
|
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
server = choice.Item.(*Server)
|
|
||||||
|
|
||||||
r.URL.Path = strings.Join(parts[3:], "/")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If none of the above exceptions are matched, we use the geographical distance based on IP
|
|
||||||
if server == nil {
|
|
||||||
server, distance, err = servers.Closest(ip)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we don't have a scheme, we'll use https by default
|
|
||||||
scheme := r.URL.Scheme
|
scheme := r.URL.Scheme
|
||||||
|
|
||||||
if scheme == "" {
|
if scheme == "" {
|
||||||
scheme = "https"
|
scheme = "https"
|
||||||
}
|
}
|
||||||
|
|
||||||
// redirectPath is a combination of server path (which can be something like /armbian)
|
|
||||||
// and the URL path.
|
|
||||||
// Example: /armbian + /some/path = /armbian/some/path
|
|
||||||
redirectPath := path.Join(server.Path, r.URL.Path)
|
redirectPath := path.Join(server.Path, r.URL.Path)
|
||||||
|
|
||||||
// If we have a dlMap, we map the url to a final path instead
|
|
||||||
if dlMap != nil {
|
if dlMap != nil {
|
||||||
if newPath, exists := dlMap[strings.TrimLeft(r.URL.Path, "/")]; exists {
|
if newPath, exists := dlMap[strings.TrimLeft(r.URL.Path, "/")]; exists {
|
||||||
downloadsMapped.Inc()
|
downloadsMapped.Inc()
|
||||||
|
@ -111,11 +59,6 @@ func redirectHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasSuffix(r.URL.Path, "/") && !strings.HasSuffix(redirectPath, "/") {
|
|
||||||
redirectPath += "/"
|
|
||||||
}
|
|
||||||
|
|
||||||
// We need to build the final url now
|
|
||||||
u := &url.URL{
|
u := &url.URL{
|
||||||
Scheme: scheme,
|
Scheme: scheme,
|
||||||
Host: server.Host,
|
Host: server.Host,
|
||||||
|
@ -125,47 +68,28 @@ func redirectHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
server.Redirects.Inc()
|
server.Redirects.Inc()
|
||||||
redirectsServed.Inc()
|
redirectsServed.Inc()
|
||||||
|
|
||||||
// If we used geographical distance, we add an X-Geo-Distance header for debug.
|
w.Header().Set("X-Geo-Distance", fmt.Sprintf("%f", distance))
|
||||||
if distance > 0 {
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
// reloadHandler is an http handler which lets us reload the server configuration
|
|
||||||
// It is only enabled when the reloadToken is set in the configuration
|
|
||||||
func reloadHandler(w http.ResponseWriter, r *http.Request) {
|
func reloadHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
expectedToken := viper.GetString("reloadToken")
|
if mapFile := viper.GetString("dl_map"); mapFile != "" {
|
||||||
|
log.WithField("file", mapFile).Info("Loading download map")
|
||||||
|
|
||||||
|
newMap, err := loadMap(mapFile)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dlMap = newMap
|
||||||
|
|
||||||
if expectedToken == "" {
|
|
||||||
w.WriteHeader(http.StatusUnauthorized)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
token := r.Header.Get("Authorization")
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
|
||||||
if token == "" || !strings.HasPrefix(token, "Bearer") || !strings.Contains(token, " ") {
|
|
||||||
w.WriteHeader(http.StatusUnauthorized)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
token = token[strings.Index(token, " ")+1:]
|
|
||||||
|
|
||||||
if token != expectedToken {
|
|
||||||
w.WriteHeader(http.StatusUnauthorized)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := reloadConfig(); err != nil {
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
w.Write([]byte(err.Error()))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
w.Write([]byte("OK"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func dlMapHandler(w http.ResponseWriter, r *http.Request) {
|
func dlMapHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
@ -178,25 +102,3 @@ 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)
|
|
||||||
}
|
|
||||||
|
|
209
main.go
209
main.go
|
@ -1,29 +1,26 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
|
||||||
"github.com/chi-middleware/logrus-logger"
|
"github.com/chi-middleware/logrus-logger"
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
lru "github.com/hashicorp/golang-lru"
|
|
||||||
"github.com/oschwald/maxminddb-golang"
|
"github.com/oschwald/maxminddb-golang"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"net/url"
|
||||||
"os/signal"
|
"strings"
|
||||||
"syscall"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
db *maxminddb.Reader
|
db *maxminddb.Reader
|
||||||
servers ServerList
|
servers ServerList
|
||||||
regionMap map[string][]*Server
|
|
||||||
hostMap map[string]*Server
|
dlMap map[string]string
|
||||||
dlMap map[string]string
|
|
||||||
topChoices int
|
|
||||||
|
|
||||||
redirectsServed = promauto.NewCounter(prometheus.CounterOpts{
|
redirectsServed = promauto.NewCounter(prometheus.CounterOpts{
|
||||||
Name: "armbian_router_redirects",
|
Name: "armbian_router_redirects",
|
||||||
|
@ -34,80 +31,64 @@ var (
|
||||||
Name: "armbian_router_download_maps",
|
Name: "armbian_router_download_maps",
|
||||||
Help: "The total number of mapped download paths",
|
Help: "The total number of mapped download paths",
|
||||||
})
|
})
|
||||||
|
|
||||||
serverCache *lru.Cache
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type LocationLookup struct {
|
// City represents a MaxmindDB city
|
||||||
|
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 {
|
|
||||||
Server string `mapstructure:"server" yaml:"server"`
|
|
||||||
Latitude float64 `mapstructure:"latitude" yaml:"latitude"`
|
|
||||||
Longitude float64 `mapstructure:"longitude" yaml:"longitude"`
|
|
||||||
Continent string `mapstructure:"continent"`
|
|
||||||
Weight int `mapstructure:"weight" yaml:"weight"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
configFlag = flag.String("config", "", "configuration file path")
|
|
||||||
flagDebug = flag.Bool("debug", false, "Enable debug logging")
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
if *flagDebug {
|
|
||||||
log.SetLevel(log.DebugLevel)
|
|
||||||
}
|
|
||||||
|
|
||||||
viper.SetDefault("bind", ":8080")
|
viper.SetDefault("bind", ":8080")
|
||||||
viper.SetDefault("cacheSize", 1024)
|
|
||||||
viper.SetDefault("topChoices", 3)
|
|
||||||
viper.SetDefault("reloadKey", randSeq(32))
|
|
||||||
|
|
||||||
viper.SetConfigName("dlrouter") // name of config file (without extension)
|
viper.SetConfigName("dlrouter") // name of config file (without extension)
|
||||||
viper.SetConfigType("yaml") // REQUIRED if the config file does not have the extension in the name
|
viper.SetConfigType("yaml") // REQUIRED if the config file does not have the extension in the name
|
||||||
viper.AddConfigPath("/etc/dlrouter/") // path to look for the config file in
|
viper.AddConfigPath("/etc/dlrouter/") // path to look for the config file in
|
||||||
viper.AddConfigPath("$HOME/.dlrouter") // call multiple times to add many search paths
|
viper.AddConfigPath("$HOME/.dlrouter") // call multiple times to add many search paths
|
||||||
viper.AddConfigPath(".") // optionally look for config in the working directory
|
viper.AddConfigPath(".") // optionally look for config in the working directory
|
||||||
|
err := viper.ReadInConfig() // Find and read the config file
|
||||||
|
|
||||||
if *configFlag != "" {
|
if err != nil { // Handle errors reading the config file
|
||||||
viper.SetConfigFile(*configFlag)
|
log.WithError(err).Fatalln("Unable to load config file")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := reloadConfig(); err != nil {
|
db, err = maxminddb.Open(viper.GetString("geodb"))
|
||||||
log.WithError(err).Fatalln("Unable to load configuration")
|
|
||||||
|
if err != nil {
|
||||||
|
log.WithError(err).Fatalln("Unable to open database")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if mapFile := viper.GetString("dl_map"); mapFile != "" {
|
||||||
|
log.WithField("file", mapFile).Info("Loading download map")
|
||||||
|
|
||||||
|
dlMap, err = loadMap(mapFile)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.WithError(err).Fatalln("Unable to load download map")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
serverList := viper.GetStringSlice("servers")
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
|
for _, server := range serverList {
|
||||||
|
wg.Add(1)
|
||||||
|
|
||||||
|
go func(server string) {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
addServer(server)
|
||||||
|
}(server)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
log.Info("Servers added, checking statuses")
|
||||||
|
|
||||||
// Start check loop
|
// Start check loop
|
||||||
go servers.checkLoop()
|
go servers.checkLoop()
|
||||||
|
|
||||||
|
@ -118,37 +99,73 @@ 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.HandleFunc("/status", statusHandler)
|
||||||
r.Get("/status", statusHandler)
|
r.HandleFunc("/mirrors", mirrorsHandler)
|
||||||
r.Get("/mirrors", legacyMirrorsHandler)
|
r.HandleFunc("/reload", reloadHandler)
|
||||||
r.Get("/mirrors/{server}.svg", mirrorStatusHandler)
|
r.HandleFunc("/dl_map", dlMapHandler)
|
||||||
r.Get("/mirrors.json", mirrorsHandler)
|
r.Handle("/metrics", promhttp.Handler())
|
||||||
r.Post("/reload", reloadHandler)
|
r.HandleFunc("/", redirectHandler)
|
||||||
r.Get("/dl_map", dlMapHandler)
|
|
||||||
r.Get("/geoip", geoIPHandler)
|
|
||||||
r.Get("/metrics", promhttp.Handler().ServeHTTP)
|
|
||||||
|
|
||||||
r.NotFound(redirectHandler)
|
http.ListenAndServe(viper.GetString("bind"), r)
|
||||||
|
}
|
||||||
go http.ListenAndServe(viper.GetString("bind"), r)
|
|
||||||
|
var metricReplacer = strings.NewReplacer(".", "_", "-", "_")
|
||||||
log.Info("Ready")
|
|
||||||
|
func addServer(server string) {
|
||||||
c := make(chan os.Signal)
|
var prefix string
|
||||||
|
|
||||||
signal.Notify(c, syscall.SIGKILL, syscall.SIGTERM, syscall.SIGHUP)
|
if !strings.HasPrefix(server, "http") {
|
||||||
|
prefix = "https://"
|
||||||
for {
|
}
|
||||||
sig := <-c
|
|
||||||
|
u, err := url.Parse(prefix + server)
|
||||||
if sig != syscall.SIGHUP {
|
|
||||||
break
|
if err != nil {
|
||||||
}
|
log.WithFields(log.Fields{
|
||||||
|
"error": err,
|
||||||
err := reloadConfig()
|
"server": server,
|
||||||
|
}).Warning("Server is invalid")
|
||||||
if err != nil {
|
return
|
||||||
log.WithError(err).Warning("Did not reload configuration due to error")
|
}
|
||||||
}
|
|
||||||
}
|
ips, err := net.LookupIP(u.Host)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"error": err,
|
||||||
|
"server": server,
|
||||||
|
}).Warning("Could not resolve address")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var city City
|
||||||
|
err = db.Lookup(ips[0], &city)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"error": err,
|
||||||
|
"server": server,
|
||||||
|
}).Warning("Could not geolocate address")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
redirects := promauto.NewCounter(prometheus.CounterOpts{
|
||||||
|
Name: "armbian_router_redirects_" + metricReplacer.Replace(u.Host),
|
||||||
|
Help: "The number of redirects for server " + u.Host,
|
||||||
|
})
|
||||||
|
|
||||||
|
servers = append(servers, &Server{
|
||||||
|
Host: u.Host,
|
||||||
|
Path: u.Path,
|
||||||
|
Latitude: city.Location.Latitude,
|
||||||
|
Longitude: city.Location.Longitude,
|
||||||
|
Redirects: redirects,
|
||||||
|
})
|
||||||
|
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"server": u.Host,
|
||||||
|
"path": u.Path,
|
||||||
|
"latitude": city.Location.Latitude,
|
||||||
|
"longitude": city.Location.Longitude,
|
||||||
|
}).Info("Added server")
|
||||||
}
|
}
|
||||||
|
|
8
map.go
8
map.go
|
@ -7,8 +7,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// loadMapFile loads a file as a map
|
func loadMap(file string) (map[string]string, error) {
|
||||||
func loadMapFile(file string) (map[string]string, error) {
|
|
||||||
f, err := os.Open(file)
|
f, err := os.Open(file)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -17,11 +16,6 @@ func loadMapFile(file string) (map[string]string, error) {
|
||||||
|
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
return loadMap(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// loadMap loads a pipe separated file of mappings
|
|
||||||
func loadMap(f io.Reader) (map[string]string, error) {
|
|
||||||
m := make(map[string]string)
|
m := make(map[string]string)
|
||||||
|
|
||||||
r := csv.NewReader(f)
|
r := csv.NewReader(f)
|
||||||
|
|
16
map_test.go
16
map_test.go
|
@ -1,16 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
. "github.com/onsi/ginkgo/v2"
|
|
||||||
. "github.com/onsi/gomega"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ = Describe("Map", func() {
|
|
||||||
It("Should successfully load the map", func() {
|
|
||||||
m, err := loadMap(strings.NewReader(`bananapi/Bullseye_current|bananapi/archive/Armbian_21.08.1_Bananapi_bullseye_current_5.10.60.img.xz|Aug 26 2021|332M`))
|
|
||||||
|
|
||||||
Expect(err).To(BeNil())
|
|
||||||
Expect(m["bananapi/Bullseye_current"]).To(Equal("bananapi/archive/Armbian_21.08.1_Bananapi_bullseye_current_5.10.60.img.xz"))
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -29,9 +29,7 @@ func RealIPMiddleware(f http.Handler) http.Handler {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
netIP := net.ParseIP(host)
|
if !net.ParseIP(host).IsPrivate() {
|
||||||
|
|
||||||
if !netIP.IsLoopback() && !netIP.IsPrivate() {
|
|
||||||
f.ServeHTTP(w, r)
|
f.ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
94
mirrors.go
94
mirrors.go
|
@ -1,94 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
_ "embed"
|
|
||||||
"encoding/json"
|
|
||||||
"github.com/go-chi/chi/v5"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// legacyMirrorsHandler will list the mirrors by region in the legacy format
|
|
||||||
// it is preferred to use mirrors.json, but this handler is here for build support
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
// mirrorsHandler is a simple handler that will return the list of servers
|
|
||||||
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
|
|
||||||
)
|
|
||||||
|
|
||||||
// mirrorStatusHandler is a fancy svg-returning handler.
|
|
||||||
// it is used to display mirror statuses on a config repo of sorts
|
|
||||||
func mirrorStatusHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
serverHost := chi.URLParam(r, "server")
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "image/svg+xml;charset=utf-8")
|
|
||||||
w.Header().Set("Cache-Control", "max-age=120")
|
|
||||||
|
|
||||||
if serverHost == "" {
|
|
||||||
w.Write(statusUnknown)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
serverHost = strings.Replace(serverHost, "_", ".", -1)
|
|
||||||
|
|
||||||
server, ok := hostMap[serverHost]
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
w.Header().Set("Content-Length", strconv.Itoa(len(statusUnknown)))
|
|
||||||
w.Write(statusUnknown)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
key := "offline"
|
|
||||||
|
|
||||||
if server.Available {
|
|
||||||
key = "online"
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Set("ETag", "\""+key+"\"")
|
|
||||||
|
|
||||||
if match := r.Header.Get("If-None-Match"); match != "" {
|
|
||||||
if strings.Trim(match, "\"") == key {
|
|
||||||
w.WriteHeader(http.StatusNotModified)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if server.Available {
|
|
||||||
w.Header().Set("Content-Length", strconv.Itoa(len(statusUp)))
|
|
||||||
w.Write(statusUp)
|
|
||||||
} else {
|
|
||||||
w.Header().Set("Content-Length", strconv.Itoa(len(statusDown)))
|
|
||||||
w.Write(statusDown)
|
|
||||||
}
|
|
||||||
}
|
|
189
servers.go
189
servers.go
|
@ -1,86 +1,81 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
|
||||||
"github.com/jmcvetta/randutil"
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"math"
|
"math"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sort"
|
"runtime"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
checkClient = &http.Client{
|
checkClient = &http.Client{
|
||||||
Timeout: 20 * time.Second,
|
Timeout: 10 * time.Second,
|
||||||
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
|
||||||
return http.ErrUseLastResponse
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
checkTLSConfig *tls.Config = nil
|
|
||||||
|
|
||||||
checks = []serverCheck{
|
|
||||||
checkHttp,
|
|
||||||
checkTLS,
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// Server represents a download server
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
Available bool `json:"available"`
|
Available bool
|
||||||
Host string `json:"host"`
|
Host string
|
||||||
Path string `json:"path"`
|
Path string
|
||||||
Latitude float64 `json:"latitude"`
|
Latitude float64
|
||||||
Longitude float64 `json:"longitude"`
|
Longitude float64
|
||||||
Weight int `json:"weight"`
|
Redirects prometheus.Counter
|
||||||
Continent string `json:"continent"`
|
|
||||||
Redirects prometheus.Counter `json:"-"`
|
|
||||||
LastChange time.Time `json:"lastChange"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type serverCheck func(server *Server, logFields log.Fields) (bool, error)
|
|
||||||
|
|
||||||
// checkStatus runs all status checks against a server
|
|
||||||
func (server *Server) checkStatus() {
|
func (server *Server) checkStatus() {
|
||||||
logFields := log.Fields{
|
req, err := http.NewRequest(http.MethodGet, "https://"+server.Host+"/"+strings.TrimLeft(server.Path, "/"), nil)
|
||||||
"host": server.Host,
|
|
||||||
|
req.Header.Set("User-Agent", "ArmbianRouter/1.0 (Go "+runtime.Version()+")")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
// This should never happen.
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"server": server.Host,
|
||||||
|
"error": err,
|
||||||
|
}).Warning("Invalid request! This should not happen, please check config.")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var res bool
|
res, err := checkClient.Do(req)
|
||||||
var err error
|
|
||||||
|
|
||||||
for _, check := range checks {
|
if err != nil {
|
||||||
res, err = check(server, logFields)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
logFields["error"] = err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !res {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !res {
|
|
||||||
if server.Available {
|
if server.Available {
|
||||||
log.WithFields(logFields).Info("Server went offline")
|
log.WithFields(log.Fields{
|
||||||
|
"server": server.Host,
|
||||||
|
"error": err,
|
||||||
|
}).Info("Server went offline")
|
||||||
|
|
||||||
server.Available = false
|
server.Available = false
|
||||||
server.LastChange = time.Now()
|
|
||||||
} else {
|
} else {
|
||||||
log.WithFields(logFields).Debug("Server is still offline")
|
log.WithFields(log.Fields{
|
||||||
|
"server": server.Host,
|
||||||
|
"error": err,
|
||||||
|
}).Debug("Server is still offline")
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
} else {
|
}
|
||||||
|
|
||||||
|
responseFields := log.Fields{
|
||||||
|
"server": server.Host,
|
||||||
|
"responseCode": res.StatusCode,
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.StatusCode == http.StatusOK || res.StatusCode == http.StatusMovedPermanently || res.StatusCode == http.StatusFound || res.StatusCode == http.StatusNotFound {
|
||||||
if !server.Available {
|
if !server.Available {
|
||||||
server.Available = true
|
server.Available = true
|
||||||
server.LastChange = time.Now()
|
log.WithFields(responseFields).Info("Server is online")
|
||||||
log.WithFields(logFields).Info("Server is online")
|
}
|
||||||
|
} else {
|
||||||
|
log.WithFields(responseFields).Debug("Server status not known")
|
||||||
|
|
||||||
|
if server.Available {
|
||||||
|
log.WithFields(responseFields).Info("Server went offline")
|
||||||
|
server.Available = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -91,8 +86,8 @@ func (s ServerList) checkLoop() {
|
||||||
t := time.NewTicker(60 * time.Second)
|
t := time.NewTicker(60 * time.Second)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
<-t.C
|
|
||||||
s.Check()
|
s.Check()
|
||||||
|
<-t.C
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,89 +110,33 @@ func (s ServerList) Check() {
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ComputedDistance is a wrapper that contains a Server and Distance.
|
// Closest will use GeoIP on the IP provided and find the closest server.
|
||||||
type ComputedDistance struct {
|
|
||||||
Server *Server
|
|
||||||
Distance float64
|
|
||||||
}
|
|
||||||
|
|
||||||
// DistanceList is a list of Computed Distances with an easy "Choices" func
|
|
||||||
type DistanceList []ComputedDistance
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
// 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) {
|
||||||
choiceInterface, exists := serverCache.Get(ip.String())
|
var city City
|
||||||
|
err := db.Lookup(ip, &city)
|
||||||
if !exists {
|
|
||||||
var city LocationLookup
|
|
||||||
err := db.Lookup(ip, &city)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, -1, err
|
|
||||||
}
|
|
||||||
|
|
||||||
c := make(DistanceList, len(s))
|
|
||||||
|
|
||||||
for i, server := range s {
|
|
||||||
if !server.Available {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
distance := Distance(city.Location.Latitude, city.Location.Longitude, server.Latitude, server.Longitude)
|
|
||||||
|
|
||||||
c[i] = ComputedDistance{
|
|
||||||
Server: server,
|
|
||||||
Distance: distance,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort by distance
|
|
||||||
sort.Slice(c, func(i int, j int) bool {
|
|
||||||
return c[i].Distance < c[j].Distance
|
|
||||||
})
|
|
||||||
|
|
||||||
choiceCount := topChoices
|
|
||||||
|
|
||||||
if len(c) < topChoices {
|
|
||||||
choiceCount = len(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
choices := make([]randutil.Choice, choiceCount)
|
|
||||||
|
|
||||||
for i, item := range c[0:choiceCount] {
|
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
dist := choice.Item.(ComputedDistance)
|
var closest *Server
|
||||||
|
var closestDistance float64 = -1
|
||||||
|
|
||||||
if !dist.Server.Available {
|
for _, server := range s {
|
||||||
// Choose a new server and refresh cache
|
if !server.Available {
|
||||||
serverCache.Remove(ip.String())
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
return s.Closest(ip)
|
distance := Distance(city.Location.Latitude, city.Location.Longitude, server.Latitude, server.Longitude)
|
||||||
|
|
||||||
|
if closestDistance == -1 || distance < closestDistance {
|
||||||
|
closestDistance = distance
|
||||||
|
closest = server
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return dist.Server, dist.Distance, nil
|
return closest, closestDistance, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// haversin(θ) function
|
// haversin(θ) function
|
||||||
|
@ -207,7 +146,7 @@ func hsin(theta float64) float64 {
|
||||||
|
|
||||||
// Distance function returns the distance (in meters) between two points of
|
// Distance function returns the distance (in meters) between two points of
|
||||||
// a given longitude and latitude relatively accurately (using a spherical
|
// a given longitude and latitude relatively accurately (using a spherical
|
||||||
// approximation of the Earth) through the Haversine Distance Formula for
|
// approximation of the Earth) through the Haversin Distance Formula for
|
||||||
// great arc distance on a sphere with accuracy for small distances
|
// great arc distance on a sphere with accuracy for small distances
|
||||||
//
|
//
|
||||||
// point coordinates are supplied in degrees and converted into rad. in the func
|
// point coordinates are supplied in degrees and converted into rad. in the func
|
||||||
|
|
Loading…
Reference in New Issue