From 12c411e203c3717a95efe420b4231bc7be5ef853 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Roy?= Date: Sun, 18 Aug 2024 17:26:46 -0700 Subject: [PATCH] feat(storage): `STORAGE_FILEPATH` option (#2416) - `STORAGE_FILEPATH=` disables storing to and reading from a local servers.json file - `STORAGE_FILEPATH` defaults to `/gluetun/servers.json` - Fix #2074 --- Dockerfile | 2 + cmd/gluetun/main.go | 2 +- internal/configuration/settings/provider.go | 4 +- .../configuration/settings/serverselection.go | 8 +-- internal/configuration/settings/settings.go | 17 ++++-- internal/configuration/settings/storage.go | 59 +++++++++++++++++++ internal/configuration/settings/vpn.go | 4 +- 7 files changed, 82 insertions(+), 14 deletions(-) create mode 100644 internal/configuration/settings/storage.go diff --git a/Dockerfile b/Dockerfile index cccdd61b..70668fe0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -206,6 +206,8 @@ ENV VPN_SERVICE_PROVIDER=pia \ PUBLICIP_PERIOD=12h \ PUBLICIP_API=ipinfo \ PUBLICIP_API_TOKEN= \ + # Storage + STORAGE_FILEPATH=/gluetun/servers.json \ # Pprof PPROF_ENABLED=no \ PPROF_BLOCK_PROFILE_RATE=0 \ diff --git a/cmd/gluetun/main.go b/cmd/gluetun/main.go index d56065c0..38c414b3 100644 --- a/cmd/gluetun/main.go +++ b/cmd/gluetun/main.go @@ -239,7 +239,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation, // TODO run this in a loop or in openvpn to reload from file without restarting storageLogger := logger.New(log.SetComponent("storage")) - storage, err := storage.New(storageLogger, constants.ServersData) + storage, err := storage.New(storageLogger, *allSettings.Storage.Filepath) if err != nil { return err } diff --git a/internal/configuration/settings/provider.go b/internal/configuration/settings/provider.go index 31a144ff..70b5d948 100644 --- a/internal/configuration/settings/provider.go +++ b/internal/configuration/settings/provider.go @@ -25,7 +25,7 @@ type Provider struct { } // TODO v4 remove pointer for receiver (because of Surfshark). -func (p *Provider) validate(vpnType string, storage Storage, warner Warner) (err error) { +func (p *Provider) validate(vpnType string, filterChoicesGetter FilterChoicesGetter, warner Warner) (err error) { // Validate Name var validNames []string if vpnType == vpn.OpenVPN { @@ -48,7 +48,7 @@ func (p *Provider) validate(vpnType string, storage Storage, warner Warner) (err return fmt.Errorf("%w for Wireguard: %w", ErrVPNProviderNameNotValid, err) } - err = p.ServerSelection.validate(p.Name, storage, warner) + err = p.ServerSelection.validate(p.Name, filterChoicesGetter, warner) if err != nil { return fmt.Errorf("server selection: %w", err) } diff --git a/internal/configuration/settings/serverselection.go b/internal/configuration/settings/serverselection.go index 66c39b13..a7874ab5 100644 --- a/internal/configuration/settings/serverselection.go +++ b/internal/configuration/settings/serverselection.go @@ -91,14 +91,14 @@ var ( ) func (ss *ServerSelection) validate(vpnServiceProvider string, - storage Storage, warner Warner) (err error) { + filterChoicesGetter FilterChoicesGetter, warner Warner) (err error) { switch ss.VPN { case vpn.OpenVPN, vpn.Wireguard: default: return fmt.Errorf("%w: %s", ErrVPNTypeNotValid, ss.VPN) } - filterChoices, err := getLocationFilterChoices(vpnServiceProvider, ss, storage, warner) + filterChoices, err := getLocationFilterChoices(vpnServiceProvider, ss, filterChoicesGetter, warner) if err != nil { return err // already wrapped error } @@ -142,9 +142,9 @@ func (ss *ServerSelection) validate(vpnServiceProvider string, } func getLocationFilterChoices(vpnServiceProvider string, - ss *ServerSelection, storage Storage, warner Warner) ( + ss *ServerSelection, filterChoicesGetter FilterChoicesGetter, warner Warner) ( filterChoices models.FilterChoices, err error) { - filterChoices = storage.GetFilterChoices(vpnServiceProvider) + filterChoices = filterChoicesGetter.GetFilterChoices(vpnServiceProvider) if vpnServiceProvider == providers.Surfshark { // // Retro compatibility diff --git a/internal/configuration/settings/settings.go b/internal/configuration/settings/settings.go index 399ad3eb..162647ea 100644 --- a/internal/configuration/settings/settings.go +++ b/internal/configuration/settings/settings.go @@ -22,6 +22,7 @@ type Settings struct { Log Log PublicIP PublicIP Shadowsocks Shadowsocks + Storage Storage System System Updater Updater Version Version @@ -29,14 +30,14 @@ type Settings struct { Pprof pprof.Settings } -type Storage interface { +type FilterChoicesGetter interface { GetFilterChoices(provider string) models.FilterChoices } // Validate validates all the settings and returns an error // if one of them is not valid. // TODO v4 remove pointer for receiver (because of Surfshark). -func (s *Settings) Validate(storage Storage, ipv6Supported bool, +func (s *Settings) Validate(filterChoicesGetter FilterChoicesGetter, ipv6Supported bool, warner Warner) (err error) { nameToValidation := map[string]func() error{ "control server": s.ControlServer.validate, @@ -47,12 +48,13 @@ func (s *Settings) Validate(storage Storage, ipv6Supported bool, "log": s.Log.validate, "public ip check": s.PublicIP.validate, "shadowsocks": s.Shadowsocks.validate, + "storage": s.Storage.validate, "system": s.System.validate, "updater": s.Updater.Validate, "version": s.Version.validate, // Pprof validation done in pprof constructor "VPN": func() error { - return s.VPN.Validate(storage, ipv6Supported, warner) + return s.VPN.Validate(filterChoicesGetter, ipv6Supported, warner) }, } @@ -76,6 +78,7 @@ func (s *Settings) copy() (copied Settings) { Log: s.Log.copy(), PublicIP: s.PublicIP.copy(), Shadowsocks: s.Shadowsocks.copy(), + Storage: s.Storage.copy(), System: s.System.copy(), Updater: s.Updater.copy(), Version: s.Version.copy(), @@ -85,7 +88,7 @@ func (s *Settings) copy() (copied Settings) { } func (s *Settings) OverrideWith(other Settings, - storage Storage, ipv6Supported bool, warner Warner) (err error) { + filterChoicesGetter FilterChoicesGetter, ipv6Supported bool, warner Warner) (err error) { patchedSettings := s.copy() patchedSettings.ControlServer.overrideWith(other.ControlServer) patchedSettings.DNS.overrideWith(other.DNS) @@ -95,12 +98,13 @@ func (s *Settings) OverrideWith(other Settings, patchedSettings.Log.overrideWith(other.Log) patchedSettings.PublicIP.overrideWith(other.PublicIP) patchedSettings.Shadowsocks.overrideWith(other.Shadowsocks) + patchedSettings.Storage.overrideWith(other.Storage) patchedSettings.System.overrideWith(other.System) patchedSettings.Updater.overrideWith(other.Updater) patchedSettings.Version.overrideWith(other.Version) patchedSettings.VPN.OverrideWith(other.VPN) patchedSettings.Pprof.OverrideWith(other.Pprof) - err = patchedSettings.Validate(storage, ipv6Supported, warner) + err = patchedSettings.Validate(filterChoicesGetter, ipv6Supported, warner) if err != nil { return err } @@ -117,6 +121,7 @@ func (s *Settings) SetDefaults() { s.Log.setDefaults() s.PublicIP.setDefaults() s.Shadowsocks.setDefaults() + s.Storage.setDefaults() s.System.setDefaults() s.Version.setDefaults() s.VPN.setDefaults() @@ -139,6 +144,7 @@ func (s Settings) toLinesNode() (node *gotree.Node) { node.AppendNode(s.Shadowsocks.toLinesNode()) node.AppendNode(s.HTTPProxy.toLinesNode()) node.AppendNode(s.ControlServer.toLinesNode()) + node.AppendNode(s.Storage.toLinesNode()) node.AppendNode(s.System.toLinesNode()) node.AppendNode(s.PublicIP.toLinesNode()) node.AppendNode(s.Updater.toLinesNode()) @@ -188,6 +194,7 @@ func (s *Settings) Read(r *reader.Reader) (err error) { "log": s.Log.read, "public ip": s.PublicIP.read, "shadowsocks": s.Shadowsocks.read, + "storage": s.Storage.read, "system": s.System.read, "updater": s.Updater.read, "version": s.Version.read, diff --git a/internal/configuration/settings/storage.go b/internal/configuration/settings/storage.go new file mode 100644 index 00000000..14563787 --- /dev/null +++ b/internal/configuration/settings/storage.go @@ -0,0 +1,59 @@ +package settings + +import ( + "fmt" + "path/filepath" + + "github.com/qdm12/gosettings" + "github.com/qdm12/gosettings/reader" + "github.com/qdm12/gotree" +) + +// Storage contains settings to configure the storage. +type Storage struct { + // Filepath is the path to the servers.json file. An empty string disables on-disk storage. + Filepath *string +} + +func (s Storage) validate() (err error) { + if *s.Filepath != "" { // optional + _, err := filepath.Abs(*s.Filepath) + if err != nil { + return fmt.Errorf("filepath is not valid: %w", err) + } + } + return nil +} + +func (s *Storage) copy() (copied Storage) { + return Storage{ + Filepath: gosettings.CopyPointer(s.Filepath), + } +} + +func (s *Storage) overrideWith(other Storage) { + s.Filepath = gosettings.OverrideWithPointer(s.Filepath, other.Filepath) +} + +func (s *Storage) setDefaults() { + const defaultFilepath = "/gluetun/servers.json" + s.Filepath = gosettings.DefaultPointer(s.Filepath, defaultFilepath) +} + +func (s Storage) String() string { + return s.toLinesNode().String() +} + +func (s Storage) toLinesNode() (node *gotree.Node) { + if *s.Filepath == "" { + return gotree.New("Storage settings: disabled") + } + node = gotree.New("Storage settings:") + node.Appendf("Filepath: %s", *s.Filepath) + return node +} + +func (s *Storage) read(r *reader.Reader) (err error) { + s.Filepath = r.Get("STORAGE_FILEPATH", reader.AcceptEmpty(true)) + return nil +} diff --git a/internal/configuration/settings/vpn.go b/internal/configuration/settings/vpn.go index 9d491bbb..aec51543 100644 --- a/internal/configuration/settings/vpn.go +++ b/internal/configuration/settings/vpn.go @@ -21,14 +21,14 @@ type VPN struct { } // TODO v4 remove pointer for receiver (because of Surfshark). -func (v *VPN) Validate(storage Storage, ipv6Supported bool, warner Warner) (err error) { +func (v *VPN) Validate(filterChoicesGetter FilterChoicesGetter, ipv6Supported bool, warner Warner) (err error) { // Validate Type validVPNTypes := []string{vpn.OpenVPN, vpn.Wireguard} if err = validate.IsOneOf(v.Type, validVPNTypes...); err != nil { return fmt.Errorf("%w: %w", ErrVPNTypeNotValid, err) } - err = v.Provider.validate(v.Type, storage, warner) + err = v.Provider.validate(v.Type, filterChoicesGetter, warner) if err != nil { return fmt.Errorf("provider settings: %w", err) }