mirror of
https://github.com/qdm12/gluetun.git
synced 2026-07-23 02:46:31 +02:00
gluetun updater only downloads files from gluetun-servers
This commit is contained in:
@@ -1,28 +1,13 @@
|
||||
package updater
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/qdm12/gluetun-servers/pkg/models"
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gluetun/internal/provider"
|
||||
)
|
||||
|
||||
type Providers interface {
|
||||
Get(providerName string) provider.Provider
|
||||
}
|
||||
|
||||
type Storage interface {
|
||||
SetServers(provider string, servers []models.Server) (err error)
|
||||
GetServersCount(provider string) (count int)
|
||||
ServersAreEqual(provider string, servers []models.Server) (equal bool)
|
||||
// Extra methods to match the provider.New storage interface
|
||||
FilterServers(provider string, selection settings.ServerSelection) (filtered []models.Server, err error)
|
||||
}
|
||||
|
||||
type Unzipper interface {
|
||||
FetchAndExtract(ctx context.Context, url string) (
|
||||
contents map[string][]byte, err error)
|
||||
}
|
||||
|
||||
type Logger interface {
|
||||
|
||||
@@ -42,15 +42,15 @@ type Logger interface {
|
||||
Error(s string)
|
||||
}
|
||||
|
||||
func NewLoop(settings settings.Updater, providers updater.Providers,
|
||||
storage updater.Storage, client *http.Client, logger Logger,
|
||||
func NewLoop(settings settings.Updater, storage updater.Storage,
|
||||
client *http.Client, logger Logger,
|
||||
) *Loop {
|
||||
return &Loop{
|
||||
state: state{
|
||||
status: constants.Stopped,
|
||||
settings: settings,
|
||||
},
|
||||
updater: updater.New(client, storage, providers, logger),
|
||||
updater: updater.New(client, storage, logger),
|
||||
logger: logger,
|
||||
start: make(chan struct{}),
|
||||
running: make(chan models.LoopStatus),
|
||||
|
||||
@@ -3,31 +3,29 @@ package updater
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/qdm12/gluetun-servers/pkg/models"
|
||||
"github.com/qdm12/gluetun-servers/pkg/updaters/common"
|
||||
)
|
||||
|
||||
type Provider interface {
|
||||
Name() string
|
||||
FetchServers(ctx context.Context, minServers int) (servers []models.Server, err error)
|
||||
}
|
||||
|
||||
func (u *Updater) updateProvider(ctx context.Context, provider Provider,
|
||||
minRatio float64,
|
||||
func (u *Updater) updateProvider(ctx context.Context, providerName string,
|
||||
manifest manifest, minRatio float64,
|
||||
) (err error) {
|
||||
providerName := provider.Name()
|
||||
providerFilepath := manifest.providerToFilepath[providerName]
|
||||
providerFileURL := buildProviderFileURL(providerName, providerFilepath)
|
||||
|
||||
var data models.Servers
|
||||
err = u.fetchJSON(ctx, providerFileURL, &data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("downloading provider file %s: %w", providerFileURL, err)
|
||||
}
|
||||
|
||||
existingServersCount := u.storage.GetServersCount(providerName)
|
||||
minServers := int(minRatio * float64(existingServersCount))
|
||||
servers, err := provider.FetchServers(ctx, minServers)
|
||||
if err != nil {
|
||||
if errors.Is(err, common.ErrNotEnoughServers) {
|
||||
u.logger.Warn("note: if running the update manually, you can use the flag " +
|
||||
"-minratio to allow the update to succeed with less servers found")
|
||||
}
|
||||
return fmt.Errorf("getting %s servers: %w", providerName, err)
|
||||
servers := data.Servers
|
||||
if len(servers) < minServers {
|
||||
return fmt.Errorf("provider %s has not enough servers from downloaded file: got %d and expected at least %d",
|
||||
providerName, len(servers), minServers)
|
||||
}
|
||||
|
||||
for _, server := range servers {
|
||||
|
||||
+92
-27
@@ -2,60 +2,59 @@ package updater
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/qdm12/gluetun-servers/pkg/updaters/common"
|
||||
"github.com/qdm12/gluetun-servers/pkg/updaters/unzip"
|
||||
"golang.org/x/text/cases"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
type Updater struct {
|
||||
providers Providers
|
||||
|
||||
// state
|
||||
storage Storage
|
||||
|
||||
// Functions for tests
|
||||
logger Logger
|
||||
timeNow func() time.Time
|
||||
client *http.Client
|
||||
unzipper Unzipper
|
||||
logger Logger
|
||||
timeNow func() time.Time
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
func New(httpClient *http.Client, storage Storage,
|
||||
providers Providers, logger Logger,
|
||||
) *Updater {
|
||||
unzipper := unzip.New(httpClient)
|
||||
func New(httpClient *http.Client, storage Storage, logger Logger) *Updater {
|
||||
return &Updater{
|
||||
providers: providers,
|
||||
storage: storage,
|
||||
logger: logger,
|
||||
timeNow: time.Now,
|
||||
client: httpClient,
|
||||
unzipper: unzipper,
|
||||
storage: storage,
|
||||
logger: logger,
|
||||
timeNow: time.Now,
|
||||
client: httpClient,
|
||||
}
|
||||
}
|
||||
|
||||
func (u *Updater) UpdateServers(ctx context.Context, providers []string,
|
||||
minRatio float64,
|
||||
) (err error) {
|
||||
const (
|
||||
serversManifestURL = "https://raw.githubusercontent.com/qdm12/gluetun-servers/main/pkg/servers/manifest.json"
|
||||
serversFilesBaseURL = "https://raw.githubusercontent.com/qdm12/gluetun-servers/main/pkg/servers/"
|
||||
)
|
||||
|
||||
func (u *Updater) UpdateServers(ctx context.Context, providers []string, minRatio float64) (err error) {
|
||||
manifest, err := u.fetchManifest(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("fetching remote manifest: %w", err)
|
||||
}
|
||||
|
||||
caser := cases.Title(language.English)
|
||||
for _, providerName := range providers {
|
||||
u.logger.Info("updating " + caser.String(providerName) + " servers...")
|
||||
|
||||
fetcher := u.providers.Get(providerName)
|
||||
// TODO support servers offering only TCP or only UDP
|
||||
// for NordVPN and PureVPN
|
||||
err := u.updateProvider(ctx, fetcher, minRatio)
|
||||
err := u.updateProvider(ctx, providerName, manifest, minRatio)
|
||||
switch {
|
||||
case err == nil:
|
||||
continue
|
||||
case errors.Is(err, common.ErrCredentialsMissing):
|
||||
u.logger.Warn(err.Error() + " - skipping update for " + providerName)
|
||||
continue
|
||||
case len(providers) == 1:
|
||||
// return the only error for the single provider.
|
||||
return err
|
||||
@@ -70,3 +69,69 @@ func (u *Updater) UpdateServers(ctx context.Context, providers []string,
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type manifest struct {
|
||||
providerToFilepath map[string]string
|
||||
}
|
||||
|
||||
func (u *Updater) fetchManifest(ctx context.Context) (m manifest, err error) {
|
||||
var raw map[string]json.RawMessage
|
||||
err = u.fetchJSON(ctx, serversManifestURL, &raw)
|
||||
if err != nil {
|
||||
return m, err
|
||||
}
|
||||
|
||||
m.providerToFilepath = make(map[string]string, len(raw))
|
||||
for provider, rawMessage := range raw {
|
||||
if provider == "version" {
|
||||
continue
|
||||
}
|
||||
|
||||
var metadata struct {
|
||||
Filepath string `json:"filepath"`
|
||||
}
|
||||
err = json.Unmarshal(rawMessage, &metadata)
|
||||
if err != nil {
|
||||
return m, fmt.Errorf("decoding manifest metadata for %s: %w", provider, err)
|
||||
}
|
||||
m.providerToFilepath[provider] = metadata.Filepath
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (u *Updater) fetchJSON(ctx context.Context, rawURL string, dst any) (err error) {
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, rawURL, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating request: %w", err)
|
||||
}
|
||||
|
||||
response, err := u.client.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("doing request: %w", err)
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
const limit = 10 * 1024 * 1024 // 10 MiB
|
||||
body, _ := io.ReadAll(io.LimitReader(response.Body, limit))
|
||||
return fmt.Errorf("HTTP status code %d for %s: %s",
|
||||
response.StatusCode, rawURL, strings.TrimSpace(string(body)))
|
||||
}
|
||||
|
||||
err = json.NewDecoder(response.Body).Decode(dst)
|
||||
if err != nil {
|
||||
return fmt.Errorf("decoding response body: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func buildProviderFileURL(providerName, filePath string) (providerFileURL string) {
|
||||
filename := path.Base(filePath)
|
||||
if filename == "." || filename == "/" || filename == "" {
|
||||
filename = providerName + ".json"
|
||||
}
|
||||
|
||||
return serversFilesBaseURL + url.PathEscape(filename)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user