From ecc80a5a9e3f5ba8c3096eb47c9ed8544a7e8867 Mon Sep 17 00:00:00 2001 From: Quentin McGaw Date: Mon, 25 Mar 2024 19:14:20 +0000 Subject: [PATCH] chore(config): upgrade to gosettings v0.4.0 - drop qdm12/govalid dependency - upgrade qdm12/ss-server to v0.6.0 - do not unset sensitive config settings (makes no sense to me) --- cmd/gluetun/main.go | 52 +++-- go.mod | 9 +- go.sum | 18 +- internal/cli/clientkey.go | 5 +- internal/cli/healthcheck.go | 7 +- internal/cli/openvpnconfig.go | 7 +- internal/configuration/settings/dns.go | 28 ++- .../configuration/settings/dnsblacklist.go | 74 ++++++- internal/configuration/settings/dot.go | 34 ++- internal/configuration/settings/errors.go | 1 + internal/configuration/settings/firewall.go | 43 ++-- internal/configuration/settings/health.go | 56 ++--- .../configuration/settings/healthywait.go | 28 ++- internal/configuration/settings/helpers.go | 5 + internal/configuration/settings/httpproxy.go | 96 +++++++-- internal/configuration/settings/log.go | 30 +-- internal/configuration/settings/openvpn.go | 90 +++++--- .../settings/openvpnselection.go | 32 ++- .../configuration/settings/portforward.go | 35 +++- internal/configuration/settings/provider.go | 46 +++- internal/configuration/settings/publicip.go | 25 ++- internal/configuration/settings/server.go | 17 +- .../configuration/settings/serverselection.go | 103 ++++++--- internal/configuration/settings/settings.go | 54 +++-- .../configuration/settings/shadowsocks.go | 52 ++++- internal/configuration/settings/system.go | 24 ++- internal/configuration/settings/unbound.go | 47 +++-- internal/configuration/settings/updater.go | 46 ++-- internal/configuration/settings/version.go | 16 +- internal/configuration/settings/vpn.go | 33 ++- internal/configuration/settings/wireguard.go | 47 +++-- .../settings/wireguardselection.go | 24 ++- internal/configuration/sources/env/dns.go | 55 ----- .../configuration/sources/env/dnsblacklist.go | 73 ------- internal/configuration/sources/env/dot.go | 29 --- .../configuration/sources/env/firewall.go | 36 ---- internal/configuration/sources/env/health.go | 35 ---- internal/configuration/sources/env/helpers.go | 33 --- .../configuration/sources/env/httproxy.go | 84 -------- internal/configuration/sources/env/log.go | 53 ----- internal/configuration/sources/env/openvpn.go | 77 ------- .../sources/env/openvpnselection.go | 26 --- .../configuration/sources/env/portforward.go | 34 --- internal/configuration/sources/env/pprof.go | 26 --- .../configuration/sources/env/provider.go | 45 ---- .../configuration/sources/env/publicip.go | 22 -- internal/configuration/sources/env/reader.go | 103 --------- internal/configuration/sources/env/server.go | 16 -- .../sources/env/serverselection.go | 92 -------- .../configuration/sources/env/shadowsocks.go | 42 ---- internal/configuration/sources/env/system.go | 22 -- internal/configuration/sources/env/unbound.go | 36 ---- internal/configuration/sources/env/updater.go | 35 ---- internal/configuration/sources/env/version.go | 14 -- internal/configuration/sources/env/vpn.go | 28 --- .../configuration/sources/env/wireguard.go | 33 --- .../sources/env/wireguardselection.go | 23 -- .../configuration/sources/files/health.go | 5 - .../configuration/sources/files/helpers.go | 36 ++-- .../sources/files/helpers_test.go | 3 - .../configuration/sources/files/interfaces.go | 5 + .../configuration/sources/files/openvpn.go | 33 --- .../configuration/sources/files/provider.go | 16 -- .../configuration/sources/files/reader.go | 91 ++++++-- .../sources/files/serverselection.go | 16 -- .../configuration/sources/files/system.go | 10 - internal/configuration/sources/files/vpn.go | 26 --- .../configuration/sources/files/wireguard.go | 139 ++++++------ .../sources/files/wireguard_test.go | 197 +++++++----------- .../sources/files/wireguardselection.go | 83 -------- .../sources/files/wireguardselection_test.go | 181 ---------------- .../configuration/sources/merge/reader.go | 69 ------ .../configuration/sources/secrets/health.go | 5 - .../configuration/sources/secrets/helpers.go | 58 +----- .../sources/secrets/helpers_test.go | 92 -------- .../sources/secrets/httpproxy.go | 27 --- .../sources/secrets/interfaces.go | 5 + .../configuration/sources/secrets/openvpn.go | 60 ------ .../configuration/sources/secrets/reader.go | 100 +++++++-- .../sources/secrets/reader_test.go | 102 +++++++++ .../sources/secrets/shadowsocks.go | 19 -- internal/configuration/sources/secrets/vpn.go | 21 -- .../sources/secrets/wireguard.go | 51 ++--- internal/httpserver/settings.go | 34 +-- internal/httpserver/settings_test.go | 74 +------ internal/portforward/service/settings.go | 10 +- internal/pprof/settings.go | 33 ++- internal/pprof/settings_test.go | 71 +------ 88 files changed, 1339 insertions(+), 2589 deletions(-) create mode 100644 internal/configuration/settings/helpers.go delete mode 100644 internal/configuration/sources/env/dns.go delete mode 100644 internal/configuration/sources/env/dnsblacklist.go delete mode 100644 internal/configuration/sources/env/dot.go delete mode 100644 internal/configuration/sources/env/firewall.go delete mode 100644 internal/configuration/sources/env/health.go delete mode 100644 internal/configuration/sources/env/helpers.go delete mode 100644 internal/configuration/sources/env/httproxy.go delete mode 100644 internal/configuration/sources/env/log.go delete mode 100644 internal/configuration/sources/env/openvpn.go delete mode 100644 internal/configuration/sources/env/openvpnselection.go delete mode 100644 internal/configuration/sources/env/portforward.go delete mode 100644 internal/configuration/sources/env/pprof.go delete mode 100644 internal/configuration/sources/env/provider.go delete mode 100644 internal/configuration/sources/env/publicip.go delete mode 100644 internal/configuration/sources/env/reader.go delete mode 100644 internal/configuration/sources/env/server.go delete mode 100644 internal/configuration/sources/env/serverselection.go delete mode 100644 internal/configuration/sources/env/shadowsocks.go delete mode 100644 internal/configuration/sources/env/system.go delete mode 100644 internal/configuration/sources/env/unbound.go delete mode 100644 internal/configuration/sources/env/updater.go delete mode 100644 internal/configuration/sources/env/version.go delete mode 100644 internal/configuration/sources/env/vpn.go delete mode 100644 internal/configuration/sources/env/wireguard.go delete mode 100644 internal/configuration/sources/env/wireguardselection.go delete mode 100644 internal/configuration/sources/files/health.go delete mode 100644 internal/configuration/sources/files/helpers_test.go create mode 100644 internal/configuration/sources/files/interfaces.go delete mode 100644 internal/configuration/sources/files/openvpn.go delete mode 100644 internal/configuration/sources/files/provider.go delete mode 100644 internal/configuration/sources/files/serverselection.go delete mode 100644 internal/configuration/sources/files/system.go delete mode 100644 internal/configuration/sources/files/vpn.go delete mode 100644 internal/configuration/sources/files/wireguardselection.go delete mode 100644 internal/configuration/sources/files/wireguardselection_test.go delete mode 100644 internal/configuration/sources/merge/reader.go delete mode 100644 internal/configuration/sources/secrets/health.go delete mode 100644 internal/configuration/sources/secrets/helpers_test.go delete mode 100644 internal/configuration/sources/secrets/httpproxy.go create mode 100644 internal/configuration/sources/secrets/interfaces.go delete mode 100644 internal/configuration/sources/secrets/openvpn.go create mode 100644 internal/configuration/sources/secrets/reader_test.go delete mode 100644 internal/configuration/sources/secrets/shadowsocks.go delete mode 100644 internal/configuration/sources/secrets/vpn.go diff --git a/cmd/gluetun/main.go b/cmd/gluetun/main.go index 5ee9694e1..8d705e068 100644 --- a/cmd/gluetun/main.go +++ b/cmd/gluetun/main.go @@ -17,9 +17,7 @@ import ( "github.com/qdm12/gluetun/internal/alpine" "github.com/qdm12/gluetun/internal/cli" "github.com/qdm12/gluetun/internal/configuration/settings" - "github.com/qdm12/gluetun/internal/configuration/sources/env" "github.com/qdm12/gluetun/internal/configuration/sources/files" - mux "github.com/qdm12/gluetun/internal/configuration/sources/merge" "github.com/qdm12/gluetun/internal/configuration/sources/secrets" "github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/dns" @@ -45,6 +43,8 @@ import ( "github.com/qdm12/gluetun/internal/updater/unzip" "github.com/qdm12/gluetun/internal/vpn" "github.com/qdm12/golibs/command" + "github.com/qdm12/gosettings/reader" + "github.com/qdm12/gosettings/reader/sources/env" "github.com/qdm12/goshutdown" "github.com/qdm12/goshutdown/goroutine" "github.com/qdm12/goshutdown/group" @@ -82,14 +82,21 @@ func main() { cli := cli.New() cmder := command.NewCmder() - secretsReader := secrets.New() - filesReader := files.New() - envReader := env.New(logger) - muxReader := mux.New(secretsReader, filesReader, envReader) + reader := reader.New(reader.Settings{ + Sources: []reader.Source{ + secrets.New(logger), + files.New(logger), + env.New(env.Settings{}), + }, + HandleDeprecatedKey: func(source, deprecatedKey, currentKey string) { + logger.Warn("You are using the old " + source + " " + deprecatedKey + + ", please consider changing it to " + currentKey) + }, + }) errorCh := make(chan error) go func() { - errorCh <- _main(ctx, buildInfo, args, logger, muxReader, tun, netLinker, cmder, cli) + errorCh <- _main(ctx, buildInfo, args, logger, reader, tun, netLinker, cmder, cli) }() var err error @@ -139,17 +146,17 @@ var ( //nolint:gocognit,gocyclo,maintidx func _main(ctx context.Context, buildInfo models.BuildInformation, - args []string, logger log.LoggerInterface, source Source, + args []string, logger log.LoggerInterface, reader *reader.Reader, tun Tun, netLinker netLinker, cmder command.RunStarter, cli clier) error { if len(args) > 1 { // cli operation switch args[1] { case "healthcheck": - return cli.HealthCheck(ctx, source, logger) + return cli.HealthCheck(ctx, reader, logger) case "clientkey": return cli.ClientKey(args[2:]) case "openvpnconfig": - return cli.OpenvpnConfig(logger, source, netLinker) + return cli.OpenvpnConfig(logger, reader, netLinker) case "update": return cli.Update(ctx, args[2:], logger) case "format-servers": @@ -180,17 +187,22 @@ func _main(ctx context.Context, buildInfo models.BuildInformation, fmt.Println(line) } - allSettings, err := source.Read() + var allSettings settings.Settings + err = allSettings.Read(reader) if err != nil { return err } + allSettings.SetDefaults() // Note: no need to validate minimal settings for the firewall: - // - global log level is parsed from source + // - global log level is parsed below // - firewall Debug and Enabled are booleans parsed from source - - logger.Patch(log.SetLevel(*allSettings.Log.Level)) - netLinker.PatchLoggerLevel(*allSettings.Log.Level) + logLevel, err := log.ParseLevel(allSettings.Log.Level) + if err != nil { + return fmt.Errorf("log level: %w", err) + } + logger.Patch(log.SetLevel(logLevel)) + netLinker.PatchLoggerLevel(logLevel) routingLogger := logger.New(log.SetComponent("routing")) if *allSettings.Firewall.Debug { // To remove in v4 @@ -578,8 +590,8 @@ type Linker interface { type clier interface { ClientKey(args []string) error FormatServers(args []string) error - OpenvpnConfig(logger cli.OpenvpnConfigLogger, source cli.Source, ipv6Checker cli.IPv6Checker) error - HealthCheck(ctx context.Context, source cli.Source, warner cli.Warner) error + OpenvpnConfig(logger cli.OpenvpnConfigLogger, reader *reader.Reader, ipv6Checker cli.IPv6Checker) error + HealthCheck(ctx context.Context, reader *reader.Reader, warner cli.Warner) error Update(ctx context.Context, args []string, logger cli.UpdaterLogger) error } @@ -587,9 +599,3 @@ type Tun interface { Check(tunDevice string) error Create(tunDevice string) error } - -type Source interface { - Read() (settings settings.Settings, err error) - ReadHealth() (health settings.Health, err error) - String() string -} diff --git a/go.mod b/go.mod index 27e684762..02f608127 100644 --- a/go.mod +++ b/go.mod @@ -10,13 +10,12 @@ require ( github.com/klauspost/pgzip v1.2.6 github.com/qdm12/dns v1.11.0 github.com/qdm12/golibs v0.0.0-20210822203818-5c568b0777b6 - github.com/qdm12/gosettings v0.4.0-rc1 + github.com/qdm12/gosettings v0.4.0-rc9.0.20240323143821-6cc9afe3e4a0 github.com/qdm12/goshutdown v0.3.0 github.com/qdm12/gosplash v0.1.0 github.com/qdm12/gotree v0.2.0 - github.com/qdm12/govalid v0.2.0-rc1 github.com/qdm12/log v0.1.0 - github.com/qdm12/ss-server v0.5.0 + github.com/qdm12/ss-server v0.6.0 github.com/qdm12/updated v0.0.0-20210603204757-205acfe6937e github.com/stretchr/testify v1.9.0 github.com/ulikunitz/xz v0.5.11 @@ -48,8 +47,10 @@ require ( go4.org/intern v0.0.0-20211027215823-ae77deb06f29 // indirect go4.org/unsafe/assume-no-moving-gc v0.0.0-20230525183740-e7c30c78aeb2 // indirect golang.org/x/crypto v0.21.0 // indirect - golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect + golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect golang.org/x/sync v0.1.0 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + kernel.org/pub/linux/libs/security/libcap/cap v1.2.69 // indirect + kernel.org/pub/linux/libs/security/libcap/psx v1.2.69 // indirect ) diff --git a/go.sum b/go.sum index 7c13a0095..06276154d 100644 --- a/go.sum +++ b/go.sum @@ -97,20 +97,18 @@ github.com/qdm12/golibs v0.0.0-20210603202746-e5494e9c2ebb/go.mod h1:15RBzkun0i8 github.com/qdm12/golibs v0.0.0-20210723175634-a75ca7fd74c2/go.mod h1:6aRbg4Z/bTbm9JfxsGXfWKHi7zsOvPfUTK1S5HuAFKg= github.com/qdm12/golibs v0.0.0-20210822203818-5c568b0777b6 h1:bge5AL7cjHJMPz+5IOz5yF01q/l8No6+lIEBieA8gMg= github.com/qdm12/golibs v0.0.0-20210822203818-5c568b0777b6/go.mod h1:6aRbg4Z/bTbm9JfxsGXfWKHi7zsOvPfUTK1S5HuAFKg= -github.com/qdm12/gosettings v0.4.0-rc1 h1:UYA92yyeDPbmZysIuG65yrpZVPtdIoRmtEHft/AyI38= -github.com/qdm12/gosettings v0.4.0-rc1/go.mod h1:JRV3opOpHvnKlIA29lKQMdYw1WSMVMfHYLLHPHol5ME= +github.com/qdm12/gosettings v0.4.0-rc9.0.20240323143821-6cc9afe3e4a0 h1:ocCLeoHvSD5ZSA1J0H1VyCzBeUzSJil4HvnhjHdceEc= +github.com/qdm12/gosettings v0.4.0-rc9.0.20240323143821-6cc9afe3e4a0/go.mod h1:uItKwGXibJp2pQ0am6MBKilpjfvYTGiH+zXHd10jFj8= github.com/qdm12/goshutdown v0.3.0 h1:pqBpJkdwlZlfTEx4QHtS8u8CXx6pG0fVo6S1N0MpSEM= github.com/qdm12/goshutdown v0.3.0/go.mod h1:EqZ46No00kCTZ5qzdd3qIzY6ayhMt24QI8Mh8LVQYmM= github.com/qdm12/gosplash v0.1.0 h1:Sfl+zIjFZFP7b0iqf2l5UkmEY97XBnaKkH3FNY6Gf7g= github.com/qdm12/gosplash v0.1.0/go.mod h1:+A3fWW4/rUeDXhY3ieBzwghKdnIPFJgD8K3qQkenJlw= github.com/qdm12/gotree v0.2.0 h1:+58ltxkNLUyHtATFereAcOjBVfY6ETqRex8XK90Fb/c= github.com/qdm12/gotree v0.2.0/go.mod h1:1SdFaqKZuI46U1apbXIf25pDMNnrPuYLEqMF/qL4lY4= -github.com/qdm12/govalid v0.2.0-rc1 h1:4iYQvU4ibrASgzelsEgZX4JyKX3UTB/DcHObzQ7BXtw= -github.com/qdm12/govalid v0.2.0-rc1/go.mod h1:/uWzVWMuS71wmbsVnlUxpQiy6EAXqm8eQ2RbyA72roQ= github.com/qdm12/log v0.1.0 h1:jYBd/xscHYpblzZAd2kjZp2YmuYHjAAfbTViJWxoPTw= github.com/qdm12/log v0.1.0/go.mod h1:Vchi5M8uBvHfPNIblN4mjXn/oSbiWguQIbsgF1zdQPI= -github.com/qdm12/ss-server v0.5.0 h1:ARAqJayohDM51BmJ/R5Yplkpo+Qxgp7xizBF1HWd7uQ= -github.com/qdm12/ss-server v0.5.0/go.mod h1:eFd8PL/uy0ZvJ4KeSUzToruJctVQoYqXk+LRy9vcOiI= +github.com/qdm12/ss-server v0.6.0 h1:OaOdCIBXx0z3DGHPT6Th0v88vGa3MtAS4oRgUsDHGZE= +github.com/qdm12/ss-server v0.6.0/go.mod h1:0BO/zEmtTiLDlmQEcjtoHTC+w+cWxwItjBuGP6TWM78= github.com/qdm12/updated v0.0.0-20210603204757-205acfe6937e h1:4q+uFLawkaQRq3yARYLsjJPZd2wYwxn4g6G/5v0xW1g= github.com/qdm12/updated v0.0.0-20210603204757-205acfe6937e/go.mod h1:UvJRGkZ9XL3/D7e7JiTTVLm1F3Cymd3/gFpD6frEpBo= github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg= @@ -156,8 +154,8 @@ golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5 golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= -golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc= -golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= +golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ= +golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -252,3 +250,7 @@ gvisor.dev/gvisor v0.0.0-20221203005347-703fd9b7fbc0/go.mod h1:Dn5idtptoW1dIos9U inet.af/netaddr v0.0.0-20210511181906-37180328850c/go.mod h1:z0nx+Dh+7N7CC8V5ayHtHGpZpxLQZZxkIaaz6HN65Ls= inet.af/netaddr v0.0.0-20220811202034-502d2d690317 h1:U2fwK6P2EqmopP/hFLTOAjWTki0qgd4GMJn5X8wOleU= inet.af/netaddr v0.0.0-20220811202034-502d2d690317/go.mod h1:OIezDfdzOgFhuw4HuWapWq2e9l0H9tK4F1j+ETRtF3k= +kernel.org/pub/linux/libs/security/libcap/cap v1.2.69 h1:N0m3tKYbkRMmDobh/47ngz+AWeV7PcfXMDi8xu3Vrag= +kernel.org/pub/linux/libs/security/libcap/cap v1.2.69/go.mod h1:Tk5Ip2TuxaWGpccL7//rAsLRH6RQ/jfqTGxuN/+i/FQ= +kernel.org/pub/linux/libs/security/libcap/psx v1.2.69 h1:IdrOs1ZgwGw5CI+BH6GgVVlOt+LAXoPyh7enr8lfaXs= +kernel.org/pub/linux/libs/security/libcap/psx v1.2.69/go.mod h1:+l6Ee2F59XiJ2I6WR5ObpC1utCQJZ/VLsEbQCD8RG24= diff --git a/internal/cli/clientkey.go b/internal/cli/clientkey.go index 6a4204ba0..e6df02c96 100644 --- a/internal/cli/clientkey.go +++ b/internal/cli/clientkey.go @@ -6,13 +6,12 @@ import ( "io" "os" "strings" - - "github.com/qdm12/gluetun/internal/configuration/sources/files" ) func (c *CLI) ClientKey(args []string) error { flagSet := flag.NewFlagSet("clientkey", flag.ExitOnError) - filepath := flagSet.String("path", files.OpenVPNClientKeyPath, "file path to the client.key file") + const openVPNClientKeyPath = "/gluetun/client.key" // TODO deduplicate? + filepath := flagSet.String("path", openVPNClientKeyPath, "file path to the client.key file") if err := flagSet.Parse(args); err != nil { return err } diff --git a/internal/cli/healthcheck.go b/internal/cli/healthcheck.go index a737baeb3..2ceaa6343 100644 --- a/internal/cli/healthcheck.go +++ b/internal/cli/healthcheck.go @@ -6,12 +6,15 @@ import ( "net/http" "time" + "github.com/qdm12/gluetun/internal/configuration/settings" "github.com/qdm12/gluetun/internal/healthcheck" + "github.com/qdm12/gosettings/reader" ) -func (c *CLI) HealthCheck(ctx context.Context, source Source, _ Warner) error { +func (c *CLI) HealthCheck(ctx context.Context, reader *reader.Reader, _ Warner) (err error) { // Extract the health server port from the configuration. - config, err := source.ReadHealth() + var config settings.Health + err = config.Read(reader) if err != nil { return err } diff --git a/internal/cli/openvpnconfig.go b/internal/cli/openvpnconfig.go index 19263817d..ccb5c1c8f 100644 --- a/internal/cli/openvpnconfig.go +++ b/internal/cli/openvpnconfig.go @@ -8,12 +8,14 @@ import ( "strings" "time" + "github.com/qdm12/gluetun/internal/configuration/settings" "github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/models" "github.com/qdm12/gluetun/internal/openvpn/extract" "github.com/qdm12/gluetun/internal/provider" "github.com/qdm12/gluetun/internal/storage" "github.com/qdm12/gluetun/internal/updater/resolver" + "github.com/qdm12/gosettings/reader" ) type OpenvpnConfigLogger interface { @@ -39,14 +41,15 @@ type IPv6Checker interface { IsIPv6Supported() (supported bool, err error) } -func (c *CLI) OpenvpnConfig(logger OpenvpnConfigLogger, source Source, +func (c *CLI) OpenvpnConfig(logger OpenvpnConfigLogger, reader *reader.Reader, ipv6Checker IPv6Checker) error { storage, err := storage.New(logger, constants.ServersData) if err != nil { return err } - allSettings, err := source.Read() + var allSettings settings.Settings + err = allSettings.Read(reader) if err != nil { return err } diff --git a/internal/configuration/settings/dns.go b/internal/configuration/settings/dns.go index b307f0dff..2d0988cb0 100644 --- a/internal/configuration/settings/dns.go +++ b/internal/configuration/settings/dns.go @@ -5,6 +5,7 @@ import ( "net/netip" "github.com/qdm12/gosettings" + "github.com/qdm12/gosettings/reader" "github.com/qdm12/gotree" ) @@ -49,14 +50,6 @@ func (d *DNS) Copy() (copied DNS) { } } -// mergeWith merges the other settings into any -// unset field of the receiver settings object. -func (d *DNS) mergeWith(other DNS) { - d.ServerAddress = gosettings.MergeWithValidator(d.ServerAddress, other.ServerAddress) - d.KeepNameserver = gosettings.MergeWithPointer(d.KeepNameserver, other.KeepNameserver) - d.DoT.mergeWith(other.DoT) -} - // overrideWith overrides fields of the receiver // settings object with any field set in the other // settings. @@ -87,3 +80,22 @@ func (d DNS) toLinesNode() (node *gotree.Node) { node.AppendNode(d.DoT.toLinesNode()) return node } + +func (d *DNS) read(r *reader.Reader) (err error) { + d.ServerAddress, err = r.NetipAddr("DNS_ADDRESS", reader.RetroKeys("DNS_PLAINTEXT_ADDRESS")) + if err != nil { + return err + } + + d.KeepNameserver, err = r.BoolPtr("DNS_KEEP_NAMESERVER") + if err != nil { + return err + } + + err = d.DoT.read(r) + if err != nil { + return fmt.Errorf("DNS over TLS settings: %w", err) + } + + return nil +} diff --git a/internal/configuration/settings/dnsblacklist.go b/internal/configuration/settings/dnsblacklist.go index 089969094..d430c5625 100644 --- a/internal/configuration/settings/dnsblacklist.go +++ b/internal/configuration/settings/dnsblacklist.go @@ -8,6 +8,7 @@ import ( "github.com/qdm12/dns/pkg/blacklist" "github.com/qdm12/gosettings" + "github.com/qdm12/gosettings/reader" "github.com/qdm12/gotree" ) @@ -63,16 +64,6 @@ func (b DNSBlacklist) copy() (copied DNSBlacklist) { } } -func (b *DNSBlacklist) mergeWith(other DNSBlacklist) { - b.BlockMalicious = gosettings.MergeWithPointer(b.BlockMalicious, other.BlockMalicious) - b.BlockAds = gosettings.MergeWithPointer(b.BlockAds, other.BlockAds) - b.BlockSurveillance = gosettings.MergeWithPointer(b.BlockSurveillance, other.BlockSurveillance) - b.AllowedHosts = gosettings.MergeWithSlice(b.AllowedHosts, other.AllowedHosts) - b.AddBlockedHosts = gosettings.MergeWithSlice(b.AddBlockedHosts, other.AddBlockedHosts) - b.AddBlockedIPs = gosettings.MergeWithSlice(b.AddBlockedIPs, other.AddBlockedIPs) - b.AddBlockedIPPrefixes = gosettings.MergeWithSlice(b.AddBlockedIPPrefixes, other.AddBlockedIPPrefixes) -} - func (b *DNSBlacklist) overrideWith(other DNSBlacklist) { b.BlockMalicious = gosettings.OverrideWithPointer(b.BlockMalicious, other.BlockMalicious) b.BlockAds = gosettings.OverrideWithPointer(b.BlockAds, other.BlockAds) @@ -136,3 +127,66 @@ func (b DNSBlacklist) toLinesNode() (node *gotree.Node) { return node } + +func (b *DNSBlacklist) read(r *reader.Reader) (err error) { + b.BlockMalicious, err = r.BoolPtr("BLOCK_MALICIOUS") + if err != nil { + return err + } + + b.BlockSurveillance, err = r.BoolPtr("BLOCK_SURVEILLANCE", + reader.RetroKeys("BLOCK_NSA")) + if err != nil { + return err + } + + b.BlockAds, err = r.BoolPtr("BLOCK_ADS") + if err != nil { + return err + } + + b.AddBlockedIPs, b.AddBlockedIPPrefixes, + err = readDoTPrivateAddresses(r) // TODO v4 split in 2 + if err != nil { + return err + } + + b.AllowedHosts = r.CSV("UNBLOCK") // TODO v4 change name + + return nil +} + +var ( + ErrPrivateAddressNotValid = errors.New("private address is not a valid IP or CIDR range") +) + +func readDoTPrivateAddresses(reader *reader.Reader) (ips []netip.Addr, + ipPrefixes []netip.Prefix, err error) { + privateAddresses := reader.CSV("DOT_PRIVATE_ADDRESS") + if len(privateAddresses) == 0 { + return nil, nil, nil + } + + ips = make([]netip.Addr, 0, len(privateAddresses)) + ipPrefixes = make([]netip.Prefix, 0, len(privateAddresses)) + + for _, privateAddress := range privateAddresses { + ip, err := netip.ParseAddr(privateAddress) + if err == nil { + ips = append(ips, ip) + continue + } + + ipPrefix, err := netip.ParsePrefix(privateAddress) + if err == nil { + ipPrefixes = append(ipPrefixes, ipPrefix) + continue + } + + return nil, nil, fmt.Errorf( + "environment variable DOT_PRIVATE_ADDRESS: %w: %s", + ErrPrivateAddressNotValid, privateAddress) + } + + return ips, ipPrefixes, nil +} diff --git a/internal/configuration/settings/dot.go b/internal/configuration/settings/dot.go index aec0f5a3f..dc3ace3b3 100644 --- a/internal/configuration/settings/dot.go +++ b/internal/configuration/settings/dot.go @@ -6,6 +6,7 @@ import ( "time" "github.com/qdm12/gosettings" + "github.com/qdm12/gosettings/reader" "github.com/qdm12/gotree" ) @@ -61,15 +62,6 @@ func (d *DoT) copy() (copied DoT) { } } -// mergeWith merges the other settings into any -// unset field of the receiver settings object. -func (d *DoT) mergeWith(other DoT) { - d.Enabled = gosettings.MergeWithPointer(d.Enabled, other.Enabled) - d.UpdatePeriod = gosettings.MergeWithPointer(d.UpdatePeriod, other.UpdatePeriod) - d.Unbound.mergeWith(other.Unbound) - d.Blacklist.mergeWith(other.Blacklist) -} - // overrideWith overrides fields of the receiver // settings object with any field set in the other // settings. @@ -111,3 +103,27 @@ func (d DoT) toLinesNode() (node *gotree.Node) { return node } + +func (d *DoT) read(reader *reader.Reader) (err error) { + d.Enabled, err = reader.BoolPtr("DOT") + if err != nil { + return err + } + + d.UpdatePeriod, err = reader.DurationPtr("DNS_UPDATE_PERIOD") + if err != nil { + return err + } + + err = d.Unbound.read(reader) + if err != nil { + return err + } + + err = d.Blacklist.read(reader) + if err != nil { + return err + } + + return nil +} diff --git a/internal/configuration/settings/errors.go b/internal/configuration/settings/errors.go index 17fa42787..73c61b840 100644 --- a/internal/configuration/settings/errors.go +++ b/internal/configuration/settings/errors.go @@ -3,6 +3,7 @@ package settings import "errors" var ( + ErrValueUnknown = errors.New("value is unknown") ErrCityNotValid = errors.New("the city specified is not valid") ErrControlServerPrivilegedPort = errors.New("cannot use privileged port without running as root") ErrCategoryNotValid = errors.New("the category specified is not valid") diff --git a/internal/configuration/settings/firewall.go b/internal/configuration/settings/firewall.go index 6a12e5ac4..3cdc3c0eb 100644 --- a/internal/configuration/settings/firewall.go +++ b/internal/configuration/settings/firewall.go @@ -5,6 +5,7 @@ import ( "net/netip" "github.com/qdm12/gosettings" + "github.com/qdm12/gosettings/reader" "github.com/qdm12/gotree" ) @@ -54,18 +55,6 @@ func (f *Firewall) copy() (copied Firewall) { } } -// mergeWith merges the other settings into any -// unset field of the receiver settings object. -// It merges values of slices together, even if they -// are set in the receiver settings. -func (f *Firewall) mergeWith(other Firewall) { - f.VPNInputPorts = gosettings.MergeWithSlice(f.VPNInputPorts, other.VPNInputPorts) - f.InputPorts = gosettings.MergeWithSlice(f.InputPorts, other.InputPorts) - f.OutboundSubnets = gosettings.MergeWithSlice(f.OutboundSubnets, other.OutboundSubnets) - f.Enabled = gosettings.MergeWithPointer(f.Enabled, other.Enabled) - f.Debug = gosettings.MergeWithPointer(f.Debug, other.Debug) -} - // overrideWith overrides fields of the receiver // settings object with any field set in the other // settings. @@ -122,3 +111,33 @@ func (f Firewall) toLinesNode() (node *gotree.Node) { return node } + +func (f *Firewall) read(r *reader.Reader) (err error) { + f.VPNInputPorts, err = r.CSVUint16("FIREWALL_VPN_INPUT_PORTS") + if err != nil { + return err + } + + f.InputPorts, err = r.CSVUint16("FIREWALL_INPUT_PORTS") + if err != nil { + return err + } + + f.OutboundSubnets, err = r.CSVNetipPrefixes( + "FIREWALL_OUTBOUND_SUBNETS", reader.RetroKeys("EXTRA_SUBNETS")) + if err != nil { + return err + } + + f.Enabled, err = r.BoolPtr("FIREWALL") + if err != nil { + return err + } + + f.Debug, err = r.BoolPtr("FIREWALL_DEBUG") + if err != nil { + return err + } + + return nil +} diff --git a/internal/configuration/settings/health.go b/internal/configuration/settings/health.go index 80cdebfcc..4035e2097 100644 --- a/internal/configuration/settings/health.go +++ b/internal/configuration/settings/health.go @@ -6,8 +6,9 @@ import ( "time" "github.com/qdm12/gosettings" + "github.com/qdm12/gosettings/reader" + "github.com/qdm12/gosettings/validate" "github.com/qdm12/gotree" - "github.com/qdm12/govalid/address" ) // Health contains settings for the healthcheck and health server. @@ -36,9 +37,7 @@ type Health struct { } func (h Health) Validate() (err error) { - uid := os.Getuid() - err = address.Validate(h.ServerAddress, - address.OptionListening(uid)) + err = validate.ListeningAddress(h.ServerAddress, os.Getuid()) if err != nil { return fmt.Errorf("server listening address is not valid: %w", err) } @@ -62,38 +61,27 @@ func (h *Health) copy() (copied Health) { } } -// MergeWith merges the other settings into any -// unset field of the receiver settings object. -func (h *Health) MergeWith(other Health) { - h.ServerAddress = gosettings.MergeWithString(h.ServerAddress, other.ServerAddress) - h.ReadHeaderTimeout = gosettings.MergeWithNumber(h.ReadHeaderTimeout, other.ReadHeaderTimeout) - h.ReadTimeout = gosettings.MergeWithNumber(h.ReadTimeout, other.ReadTimeout) - h.TargetAddress = gosettings.MergeWithString(h.TargetAddress, other.TargetAddress) - h.SuccessWait = gosettings.MergeWithNumber(h.SuccessWait, other.SuccessWait) - h.VPN.mergeWith(other.VPN) -} - // OverrideWith overrides fields of the receiver // settings object with any field set in the other // settings. func (h *Health) OverrideWith(other Health) { - h.ServerAddress = gosettings.OverrideWithString(h.ServerAddress, other.ServerAddress) - h.ReadHeaderTimeout = gosettings.OverrideWithNumber(h.ReadHeaderTimeout, other.ReadHeaderTimeout) - h.ReadTimeout = gosettings.OverrideWithNumber(h.ReadTimeout, other.ReadTimeout) - h.TargetAddress = gosettings.OverrideWithString(h.TargetAddress, other.TargetAddress) - h.SuccessWait = gosettings.OverrideWithNumber(h.SuccessWait, other.SuccessWait) + h.ServerAddress = gosettings.OverrideWithComparable(h.ServerAddress, other.ServerAddress) + h.ReadHeaderTimeout = gosettings.OverrideWithComparable(h.ReadHeaderTimeout, other.ReadHeaderTimeout) + h.ReadTimeout = gosettings.OverrideWithComparable(h.ReadTimeout, other.ReadTimeout) + h.TargetAddress = gosettings.OverrideWithComparable(h.TargetAddress, other.TargetAddress) + h.SuccessWait = gosettings.OverrideWithComparable(h.SuccessWait, other.SuccessWait) h.VPN.overrideWith(other.VPN) } func (h *Health) SetDefaults() { - h.ServerAddress = gosettings.DefaultString(h.ServerAddress, "127.0.0.1:9999") + h.ServerAddress = gosettings.DefaultComparable(h.ServerAddress, "127.0.0.1:9999") const defaultReadHeaderTimeout = 100 * time.Millisecond - h.ReadHeaderTimeout = gosettings.DefaultNumber(h.ReadHeaderTimeout, defaultReadHeaderTimeout) + h.ReadHeaderTimeout = gosettings.DefaultComparable(h.ReadHeaderTimeout, defaultReadHeaderTimeout) const defaultReadTimeout = 500 * time.Millisecond - h.ReadTimeout = gosettings.DefaultNumber(h.ReadTimeout, defaultReadTimeout) - h.TargetAddress = gosettings.DefaultString(h.TargetAddress, "cloudflare.com:443") + h.ReadTimeout = gosettings.DefaultComparable(h.ReadTimeout, defaultReadTimeout) + h.TargetAddress = gosettings.DefaultComparable(h.TargetAddress, "cloudflare.com:443") const defaultSuccessWait = 5 * time.Second - h.SuccessWait = gosettings.DefaultNumber(h.SuccessWait, defaultSuccessWait) + h.SuccessWait = gosettings.DefaultComparable(h.SuccessWait, defaultSuccessWait) h.VPN.setDefaults() } @@ -111,3 +99,21 @@ func (h Health) toLinesNode() (node *gotree.Node) { node.AppendNode(h.VPN.toLinesNode("VPN")) return node } + +func (h *Health) Read(r *reader.Reader) (err error) { + h.ServerAddress = r.String("HEALTH_SERVER_ADDRESS") + h.TargetAddress = r.String("HEALTH_TARGET_ADDRESS", + reader.RetroKeys("HEALTH_ADDRESS_TO_PING")) + + h.SuccessWait, err = r.Duration("HEALTH_SUCCESS_WAIT_DURATION") + if err != nil { + return err + } + + err = h.VPN.read(r) + if err != nil { + return fmt.Errorf("VPN health settings: %w", err) + } + + return nil +} diff --git a/internal/configuration/settings/healthywait.go b/internal/configuration/settings/healthywait.go index 2a23dc888..e30d610e8 100644 --- a/internal/configuration/settings/healthywait.go +++ b/internal/configuration/settings/healthywait.go @@ -4,6 +4,7 @@ import ( "time" "github.com/qdm12/gosettings" + "github.com/qdm12/gosettings/reader" "github.com/qdm12/gotree" ) @@ -23,8 +24,6 @@ func (h HealthyWait) validate() (err error) { return nil } -// mergeWith merges the other settings into any -// unset field of the receiver settings object. func (h *HealthyWait) copy() (copied HealthyWait) { return HealthyWait{ Initial: gosettings.CopyPointer(h.Initial), @@ -32,13 +31,6 @@ func (h *HealthyWait) copy() (copied HealthyWait) { } } -// mergeWith merges the other settings into any -// unset field of the receiver settings object. -func (h *HealthyWait) mergeWith(other HealthyWait) { - h.Initial = gosettings.MergeWithPointer(h.Initial, other.Initial) - h.Addition = gosettings.MergeWithPointer(h.Addition, other.Addition) -} - // overrideWith overrides fields of the receiver // settings object with any field set in the other // settings. @@ -64,3 +56,21 @@ func (h HealthyWait) toLinesNode(kind string) (node *gotree.Node) { node.Appendf("Additional duration: %s", *h.Addition) return node } + +func (h *HealthyWait) read(r *reader.Reader) (err error) { + h.Initial, err = r.DurationPtr( + "HEALTH_VPN_DURATION_INITIAL", + reader.RetroKeys("HEALTH_OPENVPN_DURATION_INITIAL")) + if err != nil { + return err + } + + h.Addition, err = r.DurationPtr( + "HEALTH_VPN_DURATION_ADDITION", + reader.RetroKeys("HEALTH_OPENVPN_DURATION_ADDITION")) + if err != nil { + return err + } + + return nil +} diff --git a/internal/configuration/settings/helpers.go b/internal/configuration/settings/helpers.go new file mode 100644 index 000000000..4873a0fbd --- /dev/null +++ b/internal/configuration/settings/helpers.go @@ -0,0 +1,5 @@ +package settings + +func ptrTo[T any](value T) *T { + return &value +} diff --git a/internal/configuration/settings/httpproxy.go b/internal/configuration/settings/httpproxy.go index d107a54e7..e5f0fbc48 100644 --- a/internal/configuration/settings/httpproxy.go +++ b/internal/configuration/settings/httpproxy.go @@ -3,11 +3,13 @@ package settings import ( "fmt" "os" + "strings" "time" "github.com/qdm12/gosettings" + "github.com/qdm12/gosettings/reader" + "github.com/qdm12/gosettings/validate" "github.com/qdm12/gotree" - "github.com/qdm12/govalid/address" ) // HTTPProxy contains settings to configure the HTTP proxy. @@ -44,9 +46,7 @@ type HTTPProxy struct { func (h HTTPProxy) validate() (err error) { // Do not validate user and password - - uid := os.Getuid() - err = address.Validate(h.ListeningAddress, address.OptionListening(uid)) + err = validate.ListeningAddress(h.ListeningAddress, os.Getuid()) if err != nil { return fmt.Errorf("%w: %s", ErrServerAddressNotValid, h.ListeningAddress) } @@ -67,44 +67,31 @@ func (h *HTTPProxy) copy() (copied HTTPProxy) { } } -// mergeWith merges the other settings into any -// unset field of the receiver settings object. -func (h *HTTPProxy) mergeWith(other HTTPProxy) { - h.User = gosettings.MergeWithPointer(h.User, other.User) - h.Password = gosettings.MergeWithPointer(h.Password, other.Password) - h.ListeningAddress = gosettings.MergeWithString(h.ListeningAddress, other.ListeningAddress) - h.Enabled = gosettings.MergeWithPointer(h.Enabled, other.Enabled) - h.Stealth = gosettings.MergeWithPointer(h.Stealth, other.Stealth) - h.Log = gosettings.MergeWithPointer(h.Log, other.Log) - h.ReadHeaderTimeout = gosettings.MergeWithNumber(h.ReadHeaderTimeout, other.ReadHeaderTimeout) - h.ReadTimeout = gosettings.MergeWithNumber(h.ReadTimeout, other.ReadTimeout) -} - // overrideWith overrides fields of the receiver // settings object with any field set in the other // settings. func (h *HTTPProxy) overrideWith(other HTTPProxy) { h.User = gosettings.OverrideWithPointer(h.User, other.User) h.Password = gosettings.OverrideWithPointer(h.Password, other.Password) - h.ListeningAddress = gosettings.OverrideWithString(h.ListeningAddress, other.ListeningAddress) + h.ListeningAddress = gosettings.OverrideWithComparable(h.ListeningAddress, other.ListeningAddress) h.Enabled = gosettings.OverrideWithPointer(h.Enabled, other.Enabled) h.Stealth = gosettings.OverrideWithPointer(h.Stealth, other.Stealth) h.Log = gosettings.OverrideWithPointer(h.Log, other.Log) - h.ReadHeaderTimeout = gosettings.OverrideWithNumber(h.ReadHeaderTimeout, other.ReadHeaderTimeout) - h.ReadTimeout = gosettings.OverrideWithNumber(h.ReadTimeout, other.ReadTimeout) + h.ReadHeaderTimeout = gosettings.OverrideWithComparable(h.ReadHeaderTimeout, other.ReadHeaderTimeout) + h.ReadTimeout = gosettings.OverrideWithComparable(h.ReadTimeout, other.ReadTimeout) } func (h *HTTPProxy) setDefaults() { h.User = gosettings.DefaultPointer(h.User, "") h.Password = gosettings.DefaultPointer(h.Password, "") - h.ListeningAddress = gosettings.DefaultString(h.ListeningAddress, ":8888") + h.ListeningAddress = gosettings.DefaultComparable(h.ListeningAddress, ":8888") h.Enabled = gosettings.DefaultPointer(h.Enabled, false) h.Stealth = gosettings.DefaultPointer(h.Stealth, false) h.Log = gosettings.DefaultPointer(h.Log, false) const defaultReadHeaderTimeout = time.Second - h.ReadHeaderTimeout = gosettings.DefaultNumber(h.ReadHeaderTimeout, defaultReadHeaderTimeout) + h.ReadHeaderTimeout = gosettings.DefaultComparable(h.ReadHeaderTimeout, defaultReadHeaderTimeout) const defaultReadTimeout = 3 * time.Second - h.ReadTimeout = gosettings.DefaultNumber(h.ReadTimeout, defaultReadTimeout) + h.ReadTimeout = gosettings.DefaultComparable(h.ReadTimeout, defaultReadTimeout) } func (h HTTPProxy) String() string { @@ -128,3 +115,66 @@ func (h HTTPProxy) toLinesNode() (node *gotree.Node) { return node } + +func (h *HTTPProxy) read(r *reader.Reader) (err error) { + h.User = r.Get("HTTPPROXY_USER", + reader.RetroKeys("PROXY_USER", "TINYPROXY_USER"), + reader.ForceLowercase(false)) + + h.Password = r.Get("HTTPPROXY_PASSWORD", + reader.RetroKeys("PROXY_PASSWORD", "TINYPROXY_PASSWORD"), + reader.ForceLowercase(false)) + + h.ListeningAddress, err = readHTTProxyListeningAddress(r) + if err != nil { + return err + } + + h.Enabled, err = r.BoolPtr("HTTPPROXY", reader.RetroKeys("PROXY", "TINYPROXY")) + if err != nil { + return err + } + + h.Stealth, err = r.BoolPtr("HTTPPROXY_STEALTH") + if err != nil { + return err + } + + h.Log, err = readHTTProxyLog(r) + if err != nil { + return err + } + + return nil +} + +func readHTTProxyListeningAddress(r *reader.Reader) (listeningAddress string, err error) { + // Retro-compatible keys using a port only + port, err := r.Uint16Ptr("", + reader.RetroKeys("HTTPPROXY_PORT", "TINYPROXY_PORT", "PROXY_PORT")) + if err != nil { + return "", err + } else if port != nil { + return fmt.Sprintf(":%d", *port), nil + } + const currentKey = "HTTPPROXY_LISTENING_ADDRESS" + return r.String(currentKey), nil +} + +func readHTTProxyLog(r *reader.Reader) (enabled *bool, err error) { + // Retro-compatible keys using different boolean verbs + value := r.String("", + reader.RetroKeys("PROXY_LOG", "TINYPROXY_LOG", "HTTPPROXY_LOG")) + switch strings.ToLower(value) { + case "": + const currentKey = "HTTPPROXY_LOG" + return r.BoolPtr(currentKey) + case "on", "info", "connect", "notice": + return ptrTo(true), nil + case "disabled", "no", "off": + return ptrTo(false), nil + default: + return nil, fmt.Errorf("HTTP retro-compatible proxy log setting: %w: %s", + ErrValueUnknown, value) + } +} diff --git a/internal/configuration/settings/log.go b/internal/configuration/settings/log.go index 681355769..bc3925965 100644 --- a/internal/configuration/settings/log.go +++ b/internal/configuration/settings/log.go @@ -1,7 +1,10 @@ package settings import ( + "fmt" + "github.com/qdm12/gosettings" + "github.com/qdm12/gosettings/reader" "github.com/qdm12/gotree" "github.com/qdm12/log" ) @@ -9,35 +12,33 @@ import ( // Log contains settings to configure the logger. type Log struct { // Level is the log level of the logger. - // It cannot be nil in the internal state. - Level *log.Level + // It cannot be empty in the internal state. + Level string } func (l Log) validate() (err error) { + _, err = log.ParseLevel(l.Level) + if err != nil { + return fmt.Errorf("level: %w", err) + } return nil } func (l *Log) copy() (copied Log) { return Log{ - Level: gosettings.CopyPointer(l.Level), + Level: l.Level, } } -// mergeWith merges the other settings into any -// unset field of the receiver settings object. -func (l *Log) mergeWith(other Log) { - l.Level = gosettings.MergeWithPointer(l.Level, other.Level) -} - // overrideWith overrides fields of the receiver // settings object with any field set in the other // settings. func (l *Log) overrideWith(other Log) { - l.Level = gosettings.OverrideWithPointer(l.Level, other.Level) + l.Level = gosettings.OverrideWithComparable(l.Level, other.Level) } func (l *Log) setDefaults() { - l.Level = gosettings.DefaultPointer(l.Level, log.LevelInfo) + l.Level = gosettings.DefaultComparable(l.Level, log.LevelInfo.String()) } func (l Log) String() string { @@ -46,6 +47,11 @@ func (l Log) String() string { func (l Log) toLinesNode() (node *gotree.Node) { node = gotree.New("Log settings:") - node.Appendf("Log level: %s", l.Level.String()) + node.Appendf("Log level: %s", l.Level) return node } + +func (l *Log) read(r *reader.Reader) (err error) { + l.Level = r.String("LOG_LEVEL") + return nil +} diff --git a/internal/configuration/settings/openvpn.go b/internal/configuration/settings/openvpn.go index 777f2899c..52ce17e11 100644 --- a/internal/configuration/settings/openvpn.go +++ b/internal/configuration/settings/openvpn.go @@ -4,12 +4,14 @@ import ( "encoding/base64" "fmt" "regexp" + "strings" "github.com/qdm12/gluetun/internal/constants/openvpn" "github.com/qdm12/gluetun/internal/constants/providers" "github.com/qdm12/gluetun/internal/openvpn/extract" "github.com/qdm12/gluetun/internal/provider/privateinternetaccess/presets" "github.com/qdm12/gosettings" + "github.com/qdm12/gosettings/reader" "github.com/qdm12/gosettings/validate" "github.com/qdm12/gotree" ) @@ -261,32 +263,11 @@ func (o *OpenVPN) copy() (copied OpenVPN) { } } -// mergeWith merges the other settings into any -// unset field of the receiver settings object. -func (o *OpenVPN) mergeWith(other OpenVPN) { - o.Version = gosettings.MergeWithString(o.Version, other.Version) - o.User = gosettings.MergeWithPointer(o.User, other.User) - o.Password = gosettings.MergeWithPointer(o.Password, other.Password) - o.ConfFile = gosettings.MergeWithPointer(o.ConfFile, other.ConfFile) - o.Ciphers = gosettings.MergeWithSlice(o.Ciphers, other.Ciphers) - o.Auth = gosettings.MergeWithPointer(o.Auth, other.Auth) - o.Cert = gosettings.MergeWithPointer(o.Cert, other.Cert) - o.Key = gosettings.MergeWithPointer(o.Key, other.Key) - o.EncryptedKey = gosettings.MergeWithPointer(o.EncryptedKey, other.EncryptedKey) - o.KeyPassphrase = gosettings.MergeWithPointer(o.KeyPassphrase, other.KeyPassphrase) - o.PIAEncPreset = gosettings.MergeWithPointer(o.PIAEncPreset, other.PIAEncPreset) - o.MSSFix = gosettings.MergeWithPointer(o.MSSFix, other.MSSFix) - o.Interface = gosettings.MergeWithString(o.Interface, other.Interface) - o.ProcessUser = gosettings.MergeWithString(o.ProcessUser, other.ProcessUser) - o.Verbosity = gosettings.MergeWithPointer(o.Verbosity, other.Verbosity) - o.Flags = gosettings.MergeWithSlice(o.Flags, other.Flags) -} - // overrideWith overrides fields of the receiver // settings object with any field set in the other // settings. func (o *OpenVPN) overrideWith(other OpenVPN) { - o.Version = gosettings.OverrideWithString(o.Version, other.Version) + o.Version = gosettings.OverrideWithComparable(o.Version, other.Version) o.User = gosettings.OverrideWithPointer(o.User, other.User) o.Password = gosettings.OverrideWithPointer(o.Password, other.Password) o.ConfFile = gosettings.OverrideWithPointer(o.ConfFile, other.ConfFile) @@ -298,14 +279,14 @@ func (o *OpenVPN) overrideWith(other OpenVPN) { o.KeyPassphrase = gosettings.OverrideWithPointer(o.KeyPassphrase, other.KeyPassphrase) o.PIAEncPreset = gosettings.OverrideWithPointer(o.PIAEncPreset, other.PIAEncPreset) o.MSSFix = gosettings.OverrideWithPointer(o.MSSFix, other.MSSFix) - o.Interface = gosettings.OverrideWithString(o.Interface, other.Interface) - o.ProcessUser = gosettings.OverrideWithString(o.ProcessUser, other.ProcessUser) + o.Interface = gosettings.OverrideWithComparable(o.Interface, other.Interface) + o.ProcessUser = gosettings.OverrideWithComparable(o.ProcessUser, other.ProcessUser) o.Verbosity = gosettings.OverrideWithPointer(o.Verbosity, other.Verbosity) o.Flags = gosettings.OverrideWithSlice(o.Flags, other.Flags) } func (o *OpenVPN) setDefaults(vpnProvider string) { - o.Version = gosettings.DefaultString(o.Version, openvpn.Openvpn25) + o.Version = gosettings.DefaultComparable(o.Version, openvpn.Openvpn25) o.User = gosettings.DefaultPointer(o.User, "") if vpnProvider == providers.Mullvad { o.Password = gosettings.DefaultPointer(o.Password, "m") @@ -326,8 +307,8 @@ func (o *OpenVPN) setDefaults(vpnProvider string) { } o.PIAEncPreset = gosettings.DefaultPointer(o.PIAEncPreset, defaultEncPreset) o.MSSFix = gosettings.DefaultPointer(o.MSSFix, 0) - o.Interface = gosettings.DefaultString(o.Interface, "tun0") - o.ProcessUser = gosettings.DefaultString(o.ProcessUser, "root") + o.Interface = gosettings.DefaultComparable(o.Interface, "tun0") + o.ProcessUser = gosettings.DefaultComparable(o.ProcessUser, "root") o.Verbosity = gosettings.DefaultPointer(o.Verbosity, 1) } @@ -395,3 +376,58 @@ func (o OpenVPN) WithDefaults(provider string) OpenVPN { o.setDefaults(provider) return o } + +func (o *OpenVPN) read(r *reader.Reader) (err error) { + o.Version = r.String("OPENVPN_VERSION") + o.User = r.Get("OPENVPN_USER", reader.RetroKeys("USER"), reader.ForceLowercase(false)) + o.Password = r.Get("OPENVPN_PASSWORD", reader.RetroKeys("PASSWORD"), reader.ForceLowercase(false)) + o.ConfFile = r.Get("OPENVPN_CUSTOM_CONFIG", reader.ForceLowercase(false)) + o.Ciphers = r.CSV("OPENVPN_CIPHERS", reader.RetroKeys("OPENVPN_CIPHER")) + o.Auth = r.Get("OPENVPN_AUTH") + o.Cert = r.Get("OPENVPN_CERT", reader.ForceLowercase(false)) + o.Key = r.Get("OPENVPN_KEY", reader.ForceLowercase(false)) + o.EncryptedKey = r.Get("OPENVPN_ENCRYPTED_KEY", reader.ForceLowercase(false)) + o.KeyPassphrase = r.Get("OPENVPN_KEY_PASSPHRASE", reader.ForceLowercase(false)) + o.PIAEncPreset = r.Get("PRIVATE_INTERNET_ACCESS_OPENVPN_ENCRYPTION_PRESET", + reader.RetroKeys("ENCRYPTION", "PIA_ENCRYPTION")) + + o.MSSFix, err = r.Uint16Ptr("OPENVPN_MSSFIX") + if err != nil { + return err + } + + o.Interface = r.String("VPN_INTERFACE", + reader.RetroKeys("OPENVPN_INTERFACE"), reader.ForceLowercase(false)) + + o.ProcessUser, err = readOpenVPNProcessUser(r) + if err != nil { + return err + } + + o.Verbosity, err = r.IntPtr("OPENVPN_VERBOSITY") + if err != nil { + return err + } + + flagsPtr := r.Get("OPENVPN_FLAGS", reader.ForceLowercase(false)) + if flagsPtr != nil { + o.Flags = strings.Fields(*flagsPtr) + } + + return nil +} + +func readOpenVPNProcessUser(r *reader.Reader) (processUser string, err error) { + value, err := r.BoolPtr("OPENVPN_ROOT") // Retro-compatibility + if err != nil { + return "", err + } else if value != nil { + if *value { + return "root", nil + } + const defaultNonRootUser = "nonrootuser" + return defaultNonRootUser, nil + } + + return r.String("OPENVPN_PROCESS_USER"), nil +} diff --git a/internal/configuration/settings/openvpnselection.go b/internal/configuration/settings/openvpnselection.go index 5e5df8e8e..2eb548751 100644 --- a/internal/configuration/settings/openvpnselection.go +++ b/internal/configuration/settings/openvpnselection.go @@ -9,6 +9,7 @@ import ( "github.com/qdm12/gluetun/internal/constants/providers" "github.com/qdm12/gluetun/internal/provider/privateinternetaccess/presets" "github.com/qdm12/gosettings" + "github.com/qdm12/gosettings/reader" "github.com/qdm12/gosettings/validate" "github.com/qdm12/gotree" ) @@ -146,23 +147,16 @@ func (o *OpenVPNSelection) copy() (copied OpenVPNSelection) { } } -func (o *OpenVPNSelection) mergeWith(other OpenVPNSelection) { - o.ConfFile = gosettings.MergeWithPointer(o.ConfFile, other.ConfFile) - o.Protocol = gosettings.MergeWithString(o.Protocol, other.Protocol) - o.CustomPort = gosettings.MergeWithPointer(o.CustomPort, other.CustomPort) - o.PIAEncPreset = gosettings.MergeWithPointer(o.PIAEncPreset, other.PIAEncPreset) -} - func (o *OpenVPNSelection) overrideWith(other OpenVPNSelection) { o.ConfFile = gosettings.OverrideWithPointer(o.ConfFile, other.ConfFile) - o.Protocol = gosettings.OverrideWithString(o.Protocol, other.Protocol) + o.Protocol = gosettings.OverrideWithComparable(o.Protocol, other.Protocol) o.CustomPort = gosettings.OverrideWithPointer(o.CustomPort, other.CustomPort) o.PIAEncPreset = gosettings.OverrideWithPointer(o.PIAEncPreset, other.PIAEncPreset) } func (o *OpenVPNSelection) setDefaults(vpnProvider string) { o.ConfFile = gosettings.DefaultPointer(o.ConfFile, "") - o.Protocol = gosettings.DefaultString(o.Protocol, constants.UDP) + o.Protocol = gosettings.DefaultComparable(o.Protocol, constants.UDP) o.CustomPort = gosettings.DefaultPointer(o.CustomPort, 0) var defaultEncPreset string @@ -194,3 +188,23 @@ func (o OpenVPNSelection) toLinesNode() (node *gotree.Node) { return node } + +func (o *OpenVPNSelection) read(r *reader.Reader) (err error) { + o.ConfFile = r.Get("OPENVPN_CUSTOM_CONFIG", reader.ForceLowercase(false)) + + o.Protocol = r.String("OPENVPN_PROTOCOL", reader.RetroKeys("PROTOCOL")) + if err != nil { + return err + } + + o.CustomPort, err = r.Uint16Ptr("VPN_ENDPOINT_PORT", + reader.RetroKeys("PORT", "OPENVPN_PORT")) + if err != nil { + return err + } + + o.PIAEncPreset = r.Get("PRIVATE_INTERNET_ACCESS_OPENVPN_ENCRYPTION_PRESET", + reader.RetroKeys("ENCRYPTION", "PIA_ENCRYPTION")) + + return nil +} diff --git a/internal/configuration/settings/portforward.go b/internal/configuration/settings/portforward.go index ee8abeb37..228313bd7 100644 --- a/internal/configuration/settings/portforward.go +++ b/internal/configuration/settings/portforward.go @@ -6,6 +6,7 @@ import ( "github.com/qdm12/gluetun/internal/constants/providers" "github.com/qdm12/gosettings" + "github.com/qdm12/gosettings/reader" "github.com/qdm12/gosettings/validate" "github.com/qdm12/gotree" ) @@ -72,13 +73,6 @@ func (p *PortForwarding) Copy() (copied PortForwarding) { } } -func (p *PortForwarding) mergeWith(other PortForwarding) { - p.Enabled = gosettings.MergeWithPointer(p.Enabled, other.Enabled) - p.Provider = gosettings.MergeWithPointer(p.Provider, other.Provider) - p.Filepath = gosettings.MergeWithPointer(p.Filepath, other.Filepath) - p.ListeningPort = gosettings.MergeWithPointer(p.ListeningPort, other.ListeningPort) -} - func (p *PortForwarding) OverrideWith(other PortForwarding) { p.Enabled = gosettings.OverrideWithPointer(p.Enabled, other.Enabled) p.Provider = gosettings.OverrideWithPointer(p.Provider, other.Provider) @@ -124,3 +118,30 @@ func (p PortForwarding) toLinesNode() (node *gotree.Node) { return node } + +func (p *PortForwarding) read(r *reader.Reader) (err error) { + p.Enabled, err = r.BoolPtr("VPN_PORT_FORWARDING", + reader.RetroKeys( + "PORT_FORWARDING", + "PRIVATE_INTERNET_ACCESS_VPN_PORT_FORWARDING", + )) + if err != nil { + return err + } + + p.Provider = r.Get("VPN_PORT_FORWARDING_PROVIDER") + + p.Filepath = r.Get("VPN_PORT_FORWARDING_STATUS_FILE", + reader.ForceLowercase(false), + reader.RetroKeys( + "PORT_FORWARDING_STATUS_FILE", + "PRIVATE_INTERNET_ACCESS_VPN_PORT_FORWARDING_STATUS_FILE", + )) + + p.ListeningPort, err = r.Uint16Ptr("VPN_PORT_FORWARDING_LISTENING_PORT") + if err != nil { + return err + } + + return nil +} diff --git a/internal/configuration/settings/provider.go b/internal/configuration/settings/provider.go index 24496fc46..41f5430db 100644 --- a/internal/configuration/settings/provider.go +++ b/internal/configuration/settings/provider.go @@ -2,10 +2,12 @@ package settings import ( "fmt" + "strings" "github.com/qdm12/gluetun/internal/constants/providers" "github.com/qdm12/gluetun/internal/constants/vpn" "github.com/qdm12/gosettings" + "github.com/qdm12/gosettings/reader" "github.com/qdm12/gosettings/validate" "github.com/qdm12/gotree" ) @@ -65,20 +67,14 @@ func (p *Provider) copy() (copied Provider) { } } -func (p *Provider) mergeWith(other Provider) { - p.Name = gosettings.MergeWithString(p.Name, other.Name) - p.ServerSelection.mergeWith(other.ServerSelection) - p.PortForwarding.mergeWith(other.PortForwarding) -} - func (p *Provider) overrideWith(other Provider) { - p.Name = gosettings.OverrideWithString(p.Name, other.Name) + p.Name = gosettings.OverrideWithComparable(p.Name, other.Name) p.ServerSelection.overrideWith(other.ServerSelection) p.PortForwarding.OverrideWith(other.PortForwarding) } func (p *Provider) setDefaults() { - p.Name = gosettings.DefaultString(p.Name, providers.PrivateInternetAccess) + p.Name = gosettings.DefaultComparable(p.Name, providers.PrivateInternetAccess) p.ServerSelection.setDefaults(p.Name) p.PortForwarding.setDefaults() } @@ -94,3 +90,37 @@ func (p Provider) toLinesNode() (node *gotree.Node) { node.AppendNode(p.PortForwarding.toLinesNode()) return node } + +func (p *Provider) read(r *reader.Reader, vpnType string) (err error) { + p.Name = readVPNServiceProvider(r, vpnType) + + err = p.ServerSelection.read(r, p.Name, vpnType) + if err != nil { + return fmt.Errorf("server selection: %w", err) + } + + err = p.PortForwarding.read(r) + if err != nil { + return fmt.Errorf("port forwarding: %w", err) + } + + return nil +} + +func readVPNServiceProvider(r *reader.Reader, vpnType string) (vpnProvider string) { + vpnProvider = r.String("VPN_SERVICE_PROVIDER", reader.RetroKeys("VPNSP")) + if vpnProvider == "" { + if vpnType != vpn.Wireguard && r.Get("OPENVPN_CUSTOM_CONFIG") != nil { + // retro compatibility + return providers.Custom + } + return "" + } + + vpnProvider = strings.ToLower(vpnProvider) + if vpnProvider == "pia" { // retro compatibility + return providers.PrivateInternetAccess + } + + return vpnProvider +} diff --git a/internal/configuration/settings/publicip.go b/internal/configuration/settings/publicip.go index ed89db4ec..74360296d 100644 --- a/internal/configuration/settings/publicip.go +++ b/internal/configuration/settings/publicip.go @@ -7,6 +7,7 @@ import ( "github.com/qdm12/gluetun/internal/publicip/api" "github.com/qdm12/gosettings" + "github.com/qdm12/gosettings/reader" "github.com/qdm12/gotree" ) @@ -77,17 +78,10 @@ func (p *PublicIP) copy() (copied PublicIP) { } } -func (p *PublicIP) mergeWith(other PublicIP) { - p.Period = gosettings.MergeWithPointer(p.Period, other.Period) - p.IPFilepath = gosettings.MergeWithPointer(p.IPFilepath, other.IPFilepath) - p.API = gosettings.MergeWithString(p.API, other.API) - p.APIToken = gosettings.MergeWithPointer(p.APIToken, other.APIToken) -} - func (p *PublicIP) overrideWith(other PublicIP) { p.Period = gosettings.OverrideWithPointer(p.Period, other.Period) p.IPFilepath = gosettings.OverrideWithPointer(p.IPFilepath, other.IPFilepath) - p.API = gosettings.OverrideWithString(p.API, other.API) + p.API = gosettings.OverrideWithComparable(p.API, other.API) p.APIToken = gosettings.OverrideWithPointer(p.APIToken, other.APIToken) } @@ -95,7 +89,7 @@ func (p *PublicIP) setDefaults() { const defaultPeriod = 12 * time.Hour p.Period = gosettings.DefaultPointer(p.Period, defaultPeriod) p.IPFilepath = gosettings.DefaultPointer(p.IPFilepath, "/tmp/gluetun/ip") - p.API = gosettings.DefaultString(p.API, "ipinfo") + p.API = gosettings.DefaultComparable(p.API, "ipinfo") p.APIToken = gosettings.DefaultPointer(p.APIToken, "") } @@ -129,3 +123,16 @@ func (p PublicIP) toLinesNode() (node *gotree.Node) { return node } + +func (p *PublicIP) read(r *reader.Reader) (err error) { + p.Period, err = r.DurationPtr("PUBLICIP_PERIOD") + if err != nil { + return err + } + + p.IPFilepath = r.Get("PUBLICIP_FILE", + reader.ForceLowercase(false), reader.RetroKeys("IP_STATUS_FILE")) + p.API = r.String("PUBLICIP_API") + p.APIToken = r.Get("PUBLICIP_API_TOKEN") + return nil +} diff --git a/internal/configuration/settings/server.go b/internal/configuration/settings/server.go index b86fb9552..82f2773a8 100644 --- a/internal/configuration/settings/server.go +++ b/internal/configuration/settings/server.go @@ -7,6 +7,7 @@ import ( "strconv" "github.com/qdm12/gosettings" + "github.com/qdm12/gosettings/reader" "github.com/qdm12/gotree" ) @@ -48,13 +49,6 @@ func (c *ControlServer) copy() (copied ControlServer) { } } -// mergeWith merges the other settings into any -// unset field of the receiver settings object. -func (c *ControlServer) mergeWith(other ControlServer) { - c.Address = gosettings.MergeWithPointer(c.Address, other.Address) - c.Log = gosettings.MergeWithPointer(c.Log, other.Log) -} - // overrideWith overrides fields of the receiver // settings object with any field set in the other // settings. @@ -78,3 +72,12 @@ func (c ControlServer) toLinesNode() (node *gotree.Node) { node.Appendf("Logging: %s", gosettings.BoolToYesNo(c.Log)) return node } + +func (c *ControlServer) read(r *reader.Reader) (err error) { + c.Log, err = r.BoolPtr("HTTP_CONTROL_SERVER_LOG") + if err != nil { + return err + } + c.Address = r.Get("HTTP_CONTROL_SERVER_ADDRESS") + return nil +} diff --git a/internal/configuration/settings/serverselection.go b/internal/configuration/settings/serverselection.go index 4468c4c75..66d6bf2ea 100644 --- a/internal/configuration/settings/serverselection.go +++ b/internal/configuration/settings/serverselection.go @@ -12,6 +12,7 @@ import ( "github.com/qdm12/gluetun/internal/constants/vpn" "github.com/qdm12/gluetun/internal/models" "github.com/qdm12/gosettings" + "github.com/qdm12/gosettings/reader" "github.com/qdm12/gosettings/validate" "github.com/qdm12/gotree" ) @@ -257,30 +258,8 @@ func (ss *ServerSelection) copy() (copied ServerSelection) { } } -func (ss *ServerSelection) mergeWith(other ServerSelection) { - ss.VPN = gosettings.MergeWithString(ss.VPN, other.VPN) - ss.TargetIP = gosettings.MergeWithValidator(ss.TargetIP, other.TargetIP) - ss.Countries = gosettings.MergeWithSlice(ss.Countries, other.Countries) - ss.Categories = gosettings.MergeWithSlice(ss.Categories, other.Categories) - ss.Regions = gosettings.MergeWithSlice(ss.Regions, other.Regions) - ss.Cities = gosettings.MergeWithSlice(ss.Cities, other.Cities) - ss.ISPs = gosettings.MergeWithSlice(ss.ISPs, other.ISPs) - ss.Hostnames = gosettings.MergeWithSlice(ss.Hostnames, other.Hostnames) - ss.Names = gosettings.MergeWithSlice(ss.Names, other.Names) - ss.Numbers = gosettings.MergeWithSlice(ss.Numbers, other.Numbers) - ss.OwnedOnly = gosettings.MergeWithPointer(ss.OwnedOnly, other.OwnedOnly) - ss.FreeOnly = gosettings.MergeWithPointer(ss.FreeOnly, other.FreeOnly) - ss.PremiumOnly = gosettings.MergeWithPointer(ss.PremiumOnly, other.PremiumOnly) - ss.StreamOnly = gosettings.MergeWithPointer(ss.StreamOnly, other.StreamOnly) - ss.MultiHopOnly = gosettings.MergeWithPointer(ss.MultiHopOnly, other.MultiHopOnly) - ss.PortForwardOnly = gosettings.MergeWithPointer(ss.PortForwardOnly, other.PortForwardOnly) - - ss.OpenVPN.mergeWith(other.OpenVPN) - ss.Wireguard.mergeWith(other.Wireguard) -} - func (ss *ServerSelection) overrideWith(other ServerSelection) { - ss.VPN = gosettings.OverrideWithString(ss.VPN, other.VPN) + ss.VPN = gosettings.OverrideWithComparable(ss.VPN, other.VPN) ss.TargetIP = gosettings.OverrideWithValidator(ss.TargetIP, other.TargetIP) ss.Countries = gosettings.OverrideWithSlice(ss.Countries, other.Countries) ss.Categories = gosettings.OverrideWithSlice(ss.Categories, other.Categories) @@ -301,7 +280,7 @@ func (ss *ServerSelection) overrideWith(other ServerSelection) { } func (ss *ServerSelection) setDefaults(vpnProvider string) { - ss.VPN = gosettings.DefaultString(ss.VPN, vpn.OpenVPN) + ss.VPN = gosettings.DefaultComparable(ss.VPN, vpn.OpenVPN) ss.TargetIP = gosettings.DefaultValidator(ss.TargetIP, netip.IPv4Unspecified()) ss.OwnedOnly = gosettings.DefaultPointer(ss.OwnedOnly, false) ss.FreeOnly = gosettings.DefaultPointer(ss.FreeOnly, false) @@ -394,3 +373,79 @@ func (ss ServerSelection) WithDefaults(provider string) ServerSelection { ss.setDefaults(provider) return ss } + +func (ss *ServerSelection) read(r *reader.Reader, + vpnProvider, vpnType string) (err error) { + ss.VPN = vpnType + + ss.TargetIP, err = r.NetipAddr("VPN_ENDPOINT_IP", + reader.RetroKeys("OPENVPN_TARGET_IP")) + if err != nil { + return err + } + + countriesRetroKeys := []string{"COUNTRY"} + if vpnProvider == providers.Cyberghost { + countriesRetroKeys = append(countriesRetroKeys, "REGION") + } + ss.Countries = r.CSV("SERVER_COUNTRIES", reader.RetroKeys(countriesRetroKeys...)) + + ss.Regions = r.CSV("SERVER_REGIONS", reader.RetroKeys("REGION")) + ss.Cities = r.CSV("SERVER_CITIES", reader.RetroKeys("CITY")) + ss.ISPs = r.CSV("ISP") + ss.Hostnames = r.CSV("SERVER_HOSTNAMES", reader.RetroKeys("SERVER_HOSTNAME")) + ss.Names = r.CSV("SERVER_NAMES", reader.RetroKeys("SERVER_NAME")) + ss.Numbers, err = r.CSVUint16("SERVER_NUMBER") + ss.Categories = r.CSV("SERVER_CATEGORIES") + if err != nil { + return err + } + + // Mullvad only + ss.OwnedOnly, err = r.BoolPtr("OWNED_ONLY", reader.RetroKeys("OWNED")) + if err != nil { + return err + } + + // VPNUnlimited and ProtonVPN only + ss.FreeOnly, err = r.BoolPtr("FREE_ONLY") + if err != nil { + return err + } + + // VPNSecure only + ss.PremiumOnly, err = r.BoolPtr("PREMIUM_ONLY") + if err != nil { + return err + } + + // Surfshark only + ss.MultiHopOnly, err = r.BoolPtr("MULTIHOP_ONLY") + if err != nil { + return err + } + + // VPNUnlimited only + ss.StreamOnly, err = r.BoolPtr("STREAM_ONLY") + if err != nil { + return err + } + + // PIA only + ss.PortForwardOnly, err = r.BoolPtr("PORT_FORWARD_ONLY") + if err != nil { + return err + } + + err = ss.OpenVPN.read(r) + if err != nil { + return err + } + + err = ss.Wireguard.read(r) + if err != nil { + return err + } + + return nil +} diff --git a/internal/configuration/settings/settings.go b/internal/configuration/settings/settings.go index 5cf66c918..988d95daa 100644 --- a/internal/configuration/settings/settings.go +++ b/internal/configuration/settings/settings.go @@ -2,12 +2,14 @@ package settings import ( "fmt" + "net/netip" "github.com/qdm12/gluetun/internal/configuration/settings/helpers" "github.com/qdm12/gluetun/internal/constants/providers" "github.com/qdm12/gluetun/internal/constants/vpn" "github.com/qdm12/gluetun/internal/models" "github.com/qdm12/gluetun/internal/pprof" + "github.com/qdm12/gosettings/reader" "github.com/qdm12/gotree" ) @@ -81,22 +83,6 @@ func (s *Settings) copy() (copied Settings) { } } -func (s *Settings) MergeWith(other Settings) { - s.ControlServer.mergeWith(other.ControlServer) - s.DNS.mergeWith(other.DNS) - s.Firewall.mergeWith(other.Firewall) - s.Health.MergeWith(other.Health) - s.HTTPProxy.mergeWith(other.HTTPProxy) - s.Log.mergeWith(other.Log) - s.PublicIP.mergeWith(other.PublicIP) - s.Shadowsocks.mergeWith(other.Shadowsocks) - s.System.mergeWith(other.System) - s.Updater.mergeWith(other.Updater) - s.Version.mergeWith(other.Version) - s.VPN.mergeWith(other.VPN) - s.Pprof.MergeWith(other.Pprof) -} - func (s *Settings) OverrideWith(other Settings, storage Storage, ipv6Supported bool) (err error) { patchedSettings := s.copy() @@ -179,5 +165,41 @@ func (s Settings) Warnings() (warnings []string) { "by creating an issue, attaching the new certificate and we will update Gluetun.") } + // TODO remove in v4 + if s.DNS.ServerAddress.Unmap().Compare(netip.AddrFrom4([4]byte{127, 0, 0, 1})) != 0 { + warnings = append(warnings, "DNS address is set to "+s.DNS.ServerAddress.String()+ + " so the DNS over TLS (DoT) server will not be used."+ + " The default value changed to 127.0.0.1 so it uses the internal DoT serves."+ + " If the DoT server fails to start, the IPv4 address of the first plaintext DNS server"+ + " corresponding to the first DoT provider chosen is used.") + } + return warnings } + +func (s *Settings) Read(r *reader.Reader) (err error) { + readFunctions := map[string]func(r *reader.Reader) error{ + "control server": s.ControlServer.read, + "DNS": s.DNS.read, + "firewall": s.Firewall.read, + "health": s.Health.Read, + "http proxy": s.HTTPProxy.read, + "log": s.Log.read, + "public ip": s.PublicIP.read, + "shadowsocks": s.Shadowsocks.read, + "system": s.System.read, + "updater": s.Updater.read, + "version": s.Version.read, + "VPN": s.VPN.read, + "profiling": s.Pprof.Read, + } + + for name, read := range readFunctions { + err = read(r) + if err != nil { + return fmt.Errorf("reading %s settings: %w", name, err) + } + } + + return nil +} diff --git a/internal/configuration/settings/shadowsocks.go b/internal/configuration/settings/shadowsocks.go index a27c43b94..7e11b74e7 100644 --- a/internal/configuration/settings/shadowsocks.go +++ b/internal/configuration/settings/shadowsocks.go @@ -1,7 +1,10 @@ package settings import ( + "fmt" + "github.com/qdm12/gosettings" + "github.com/qdm12/gosettings/reader" "github.com/qdm12/gotree" "github.com/qdm12/ss-server/pkg/tcpudp" ) @@ -26,13 +29,6 @@ func (s *Shadowsocks) copy() (copied Shadowsocks) { } } -// mergeWith merges the other settings into any -// unset field of the receiver settings object. -func (s *Shadowsocks) mergeWith(other Shadowsocks) { - s.Enabled = gosettings.MergeWithPointer(s.Enabled, other.Enabled) - s.Settings = s.Settings.MergeWith(other.Settings) -} - // overrideWith overrides fields of the receiver // settings object with any field set in the other // settings. @@ -59,10 +55,44 @@ func (s Shadowsocks) toLinesNode() (node *gotree.Node) { } // TODO have ToLinesNode in qdm12/ss-server - node.Appendf("Listening address: %s", *s.Address) - node.Appendf("Cipher: %s", s.CipherName) - node.Appendf("Password: %s", gosettings.ObfuscateKey(*s.Password)) - node.Appendf("Log addresses: %s", gosettings.BoolToYesNo(s.LogAddresses)) + node.Appendf("Listening address: %s", *s.Settings.Address) + node.Appendf("Cipher: %s", s.Settings.CipherName) + node.Appendf("Password: %s", gosettings.ObfuscateKey(*s.Settings.Password)) + node.Appendf("Log addresses: %s", gosettings.BoolToYesNo(s.Settings.LogAddresses)) return node } + +func (s *Shadowsocks) read(r *reader.Reader) (err error) { + s.Enabled, err = r.BoolPtr("SHADOWSOCKS") + if err != nil { + return err + } + + s.Settings.Address, err = readShadowsocksAddress(r) + if err != nil { + return err + } + s.Settings.LogAddresses, err = r.BoolPtr("SHADOWSOCKS_LOG") + if err != nil { + return err + } + s.Settings.CipherName = r.String("SHADOWSOCKS_CIPHER", + reader.RetroKeys("SHADOWSOCKS_METHOD")) + s.Settings.Password = r.Get("SHADOWSOCKS_PASSWORD", + reader.ForceLowercase(false)) + + return nil +} + +func readShadowsocksAddress(r *reader.Reader) (address *string, err error) { + const currentKey = "SHADOWSOCKS_LISTENING_ADDRESS" + port, err := r.Uint16Ptr("SHADOWSOCKS_PORT", reader.IsRetro(currentKey)) // retro-compatibility + if err != nil { + return nil, err + } else if port != nil { + return ptrTo(fmt.Sprintf(":%d", *port)), nil + } + + return r.Get(currentKey), nil +} diff --git a/internal/configuration/settings/system.go b/internal/configuration/settings/system.go index 43e16b856..e161ad3c8 100644 --- a/internal/configuration/settings/system.go +++ b/internal/configuration/settings/system.go @@ -2,6 +2,7 @@ package settings import ( "github.com/qdm12/gosettings" + "github.com/qdm12/gosettings/reader" "github.com/qdm12/gotree" ) @@ -25,16 +26,10 @@ func (s *System) copy() (copied System) { } } -func (s *System) mergeWith(other System) { - s.PUID = gosettings.MergeWithPointer(s.PUID, other.PUID) - s.PGID = gosettings.MergeWithPointer(s.PGID, other.PGID) - s.Timezone = gosettings.MergeWithString(s.Timezone, other.Timezone) -} - func (s *System) overrideWith(other System) { s.PUID = gosettings.OverrideWithPointer(s.PUID, other.PUID) s.PGID = gosettings.OverrideWithPointer(s.PGID, other.PGID) - s.Timezone = gosettings.OverrideWithString(s.Timezone, other.Timezone) + s.Timezone = gosettings.OverrideWithComparable(s.Timezone, other.Timezone) } func (s *System) setDefaults() { @@ -59,3 +54,18 @@ func (s System) toLinesNode() (node *gotree.Node) { return node } + +func (s *System) read(r *reader.Reader) (err error) { + s.PUID, err = r.Uint32Ptr("PUID", reader.RetroKeys("UID")) + if err != nil { + return err + } + + s.PGID, err = r.Uint32Ptr("PGID", reader.RetroKeys("GID")) + if err != nil { + return err + } + + s.Timezone = r.String("TZ") + return nil +} diff --git a/internal/configuration/settings/unbound.go b/internal/configuration/settings/unbound.go index b18af5bfe..425348f7b 100644 --- a/internal/configuration/settings/unbound.go +++ b/internal/configuration/settings/unbound.go @@ -8,6 +8,7 @@ import ( "github.com/qdm12/dns/pkg/provider" "github.com/qdm12/dns/pkg/unbound" "github.com/qdm12/gosettings" + "github.com/qdm12/gosettings/reader" "github.com/qdm12/gotree" ) @@ -49,7 +50,7 @@ func (u *Unbound) setDefaults() { } } - u.Username = gosettings.DefaultString(u.Username, "root") + u.Username = gosettings.DefaultComparable(u.Username, "root") } var ( @@ -105,17 +106,6 @@ func (u Unbound) copy() (copied Unbound) { } } -func (u *Unbound) mergeWith(other Unbound) { - u.Providers = gosettings.MergeWithSlice(u.Providers, other.Providers) - u.Caching = gosettings.MergeWithPointer(u.Caching, other.Caching) - u.IPv6 = gosettings.MergeWithPointer(u.IPv6, other.IPv6) - u.VerbosityLevel = gosettings.MergeWithPointer(u.VerbosityLevel, other.VerbosityLevel) - u.VerbosityDetailsLevel = gosettings.MergeWithPointer(u.VerbosityDetailsLevel, other.VerbosityDetailsLevel) - u.ValidationLogLevel = gosettings.MergeWithPointer(u.ValidationLogLevel, other.ValidationLogLevel) - u.Username = gosettings.MergeWithString(u.Username, other.Username) - u.Allowed = gosettings.MergeWithSlice(u.Allowed, other.Allowed) -} - func (u *Unbound) overrideWith(other Unbound) { u.Providers = gosettings.OverrideWithSlice(u.Providers, other.Providers) u.Caching = gosettings.OverrideWithPointer(u.Caching, other.Caching) @@ -123,7 +113,7 @@ func (u *Unbound) overrideWith(other Unbound) { u.VerbosityLevel = gosettings.OverrideWithPointer(u.VerbosityLevel, other.VerbosityLevel) u.VerbosityDetailsLevel = gosettings.OverrideWithPointer(u.VerbosityDetailsLevel, other.VerbosityDetailsLevel) u.ValidationLogLevel = gosettings.OverrideWithPointer(u.ValidationLogLevel, other.ValidationLogLevel) - u.Username = gosettings.OverrideWithString(u.Username, other.Username) + u.Username = gosettings.OverrideWithComparable(u.Username, other.Username) u.Allowed = gosettings.OverrideWithSlice(u.Allowed, other.Allowed) } @@ -200,3 +190,34 @@ func (u Unbound) toLinesNode() (node *gotree.Node) { return node } + +func (u *Unbound) read(reader *reader.Reader) (err error) { + u.Providers = reader.CSV("DOT_PROVIDERS") + + u.Caching, err = reader.BoolPtr("DOT_CACHING") + if err != nil { + return err + } + + u.IPv6, err = reader.BoolPtr("DOT_IPV6") + if err != nil { + return err + } + + u.VerbosityLevel, err = reader.Uint8Ptr("DOT_VERBOSITY") + if err != nil { + return err + } + + u.VerbosityDetailsLevel, err = reader.Uint8Ptr("DOT_VERBOSITY_DETAILS") + if err != nil { + return err + } + + u.ValidationLogLevel, err = reader.Uint8Ptr("DOT_VALIDATION_LOGLEVEL") + if err != nil { + return err + } + + return nil +} diff --git a/internal/configuration/settings/updater.go b/internal/configuration/settings/updater.go index 8c6de5405..b4bff84df 100644 --- a/internal/configuration/settings/updater.go +++ b/internal/configuration/settings/updater.go @@ -7,6 +7,7 @@ import ( "github.com/qdm12/gluetun/internal/constants/providers" "github.com/qdm12/gosettings" + "github.com/qdm12/gosettings/reader" "github.com/qdm12/gosettings/validate" "github.com/qdm12/gotree" ) @@ -64,28 +65,19 @@ func (u *Updater) copy() (copied Updater) { } } -// mergeWith merges the other settings into any -// unset field of the receiver settings object. -func (u *Updater) mergeWith(other Updater) { - u.Period = gosettings.MergeWithPointer(u.Period, other.Period) - u.DNSAddress = gosettings.MergeWithString(u.DNSAddress, other.DNSAddress) - u.MinRatio = gosettings.MergeWithNumber(u.MinRatio, other.MinRatio) - u.Providers = gosettings.MergeWithSlice(u.Providers, other.Providers) -} - // overrideWith overrides fields of the receiver // settings object with any field set in the other // settings. func (u *Updater) overrideWith(other Updater) { u.Period = gosettings.OverrideWithPointer(u.Period, other.Period) - u.DNSAddress = gosettings.OverrideWithString(u.DNSAddress, other.DNSAddress) - u.MinRatio = gosettings.OverrideWithNumber(u.MinRatio, other.MinRatio) + u.DNSAddress = gosettings.OverrideWithComparable(u.DNSAddress, other.DNSAddress) + u.MinRatio = gosettings.OverrideWithComparable(u.MinRatio, other.MinRatio) u.Providers = gosettings.OverrideWithSlice(u.Providers, other.Providers) } func (u *Updater) SetDefaults(vpnProvider string) { u.Period = gosettings.DefaultPointer(u.Period, 0) - u.DNSAddress = gosettings.DefaultString(u.DNSAddress, "1.1.1.1:53") + u.DNSAddress = gosettings.DefaultComparable(u.DNSAddress, "1.1.1.1:53") if u.MinRatio == 0 { const defaultMinRatio = 0.8 @@ -114,3 +106,33 @@ func (u Updater) toLinesNode() (node *gotree.Node) { return node } + +func (u *Updater) read(r *reader.Reader) (err error) { + u.Period, err = r.DurationPtr("UPDATER_PERIOD") + if err != nil { + return err + } + + u.DNSAddress, err = readUpdaterDNSAddress() + if err != nil { + return err + } + + u.MinRatio, err = r.Float64("UPDATER_MIN_RATIO") + if err != nil { + return err + } + + u.Providers = r.CSV("UPDATER_VPN_SERVICE_PROVIDERS") + + return nil +} + +func readUpdaterDNSAddress() (address string, err error) { + // TODO this is currently using Cloudflare in + // plaintext to not be blocked by DNS over TLS by default. + // If a plaintext address is set in the DNS settings, this one will be used. + // use custom future encrypted DNS written in Go without blocking + // as it's too much trouble to start another parallel unbound instance for now. + return "", nil +} diff --git a/internal/configuration/settings/version.go b/internal/configuration/settings/version.go index 47f11310a..6a4acde4f 100644 --- a/internal/configuration/settings/version.go +++ b/internal/configuration/settings/version.go @@ -2,6 +2,7 @@ package settings import ( "github.com/qdm12/gosettings" + "github.com/qdm12/gosettings/reader" "github.com/qdm12/gotree" ) @@ -23,12 +24,6 @@ func (v *Version) copy() (copied Version) { } } -// mergeWith merges the other settings into any -// unset field of the receiver settings object. -func (v *Version) mergeWith(other Version) { - v.Enabled = gosettings.MergeWithPointer(v.Enabled, other.Enabled) -} - // overrideWith overrides fields of the receiver // settings object with any field set in the other // settings. @@ -51,3 +46,12 @@ func (v Version) toLinesNode() (node *gotree.Node) { return node } + +func (v *Version) read(r *reader.Reader) (err error) { + v.Enabled, err = r.BoolPtr("VERSION_INFORMATION") + if err != nil { + return err + } + + return nil +} diff --git a/internal/configuration/settings/vpn.go b/internal/configuration/settings/vpn.go index 87696ed48..ba2fd93a9 100644 --- a/internal/configuration/settings/vpn.go +++ b/internal/configuration/settings/vpn.go @@ -5,6 +5,7 @@ import ( "github.com/qdm12/gluetun/internal/constants/vpn" "github.com/qdm12/gosettings" + "github.com/qdm12/gosettings/reader" "github.com/qdm12/gosettings/validate" "github.com/qdm12/gotree" ) @@ -56,22 +57,15 @@ func (v *VPN) Copy() (copied VPN) { } } -func (v *VPN) mergeWith(other VPN) { - v.Type = gosettings.MergeWithString(v.Type, other.Type) - v.Provider.mergeWith(other.Provider) - v.OpenVPN.mergeWith(other.OpenVPN) - v.Wireguard.mergeWith(other.Wireguard) -} - func (v *VPN) OverrideWith(other VPN) { - v.Type = gosettings.OverrideWithString(v.Type, other.Type) + v.Type = gosettings.OverrideWithComparable(v.Type, other.Type) v.Provider.overrideWith(other.Provider) v.OpenVPN.overrideWith(other.OpenVPN) v.Wireguard.overrideWith(other.Wireguard) } func (v *VPN) setDefaults() { - v.Type = gosettings.DefaultString(v.Type, vpn.OpenVPN) + v.Type = gosettings.DefaultComparable(v.Type, vpn.OpenVPN) v.Provider.setDefaults() v.OpenVPN.setDefaults(v.Provider.Name) v.Wireguard.setDefaults(v.Provider.Name) @@ -94,3 +88,24 @@ func (v VPN) toLinesNode() (node *gotree.Node) { return node } + +func (v *VPN) read(r *reader.Reader) (err error) { + v.Type = r.String("VPN_TYPE") + + err = v.Provider.read(r, v.Type) + if err != nil { + return fmt.Errorf("VPN provider: %w", err) + } + + err = v.OpenVPN.read(r) + if err != nil { + return fmt.Errorf("OpenVPN: %w", err) + } + + err = v.Wireguard.read(r) + if err != nil { + return fmt.Errorf("wireguard: %w", err) + } + + return nil +} diff --git a/internal/configuration/settings/wireguard.go b/internal/configuration/settings/wireguard.go index 8d1e47b69..4ab06f1e9 100644 --- a/internal/configuration/settings/wireguard.go +++ b/internal/configuration/settings/wireguard.go @@ -8,6 +8,7 @@ import ( "github.com/qdm12/gluetun/internal/configuration/settings/helpers" "github.com/qdm12/gluetun/internal/constants/providers" "github.com/qdm12/gosettings" + "github.com/qdm12/gosettings/reader" "github.com/qdm12/gosettings/validate" "github.com/qdm12/gotree" "golang.zx2c4.com/wireguard/wgctrl/wgtypes" @@ -147,24 +148,14 @@ func (w *Wireguard) copy() (copied Wireguard) { } } -func (w *Wireguard) mergeWith(other Wireguard) { - w.PrivateKey = gosettings.MergeWithPointer(w.PrivateKey, other.PrivateKey) - w.PreSharedKey = gosettings.MergeWithPointer(w.PreSharedKey, other.PreSharedKey) - w.Addresses = gosettings.MergeWithSlice(w.Addresses, other.Addresses) - w.AllowedIPs = gosettings.MergeWithSlice(w.AllowedIPs, other.AllowedIPs) - w.Interface = gosettings.MergeWithString(w.Interface, other.Interface) - w.MTU = gosettings.MergeWithNumber(w.MTU, other.MTU) - w.Implementation = gosettings.MergeWithString(w.Implementation, other.Implementation) -} - func (w *Wireguard) overrideWith(other Wireguard) { w.PrivateKey = gosettings.OverrideWithPointer(w.PrivateKey, other.PrivateKey) w.PreSharedKey = gosettings.OverrideWithPointer(w.PreSharedKey, other.PreSharedKey) w.Addresses = gosettings.OverrideWithSlice(w.Addresses, other.Addresses) w.AllowedIPs = gosettings.OverrideWithSlice(w.AllowedIPs, other.AllowedIPs) - w.Interface = gosettings.OverrideWithString(w.Interface, other.Interface) - w.MTU = gosettings.OverrideWithNumber(w.MTU, other.MTU) - w.Implementation = gosettings.OverrideWithString(w.Implementation, other.Implementation) + w.Interface = gosettings.OverrideWithComparable(w.Interface, other.Interface) + w.MTU = gosettings.OverrideWithComparable(w.MTU, other.MTU) + w.Implementation = gosettings.OverrideWithComparable(w.Implementation, other.Implementation) } func (w *Wireguard) setDefaults(vpnProvider string) { @@ -180,10 +171,10 @@ func (w *Wireguard) setDefaults(vpnProvider string) { netip.PrefixFrom(netip.IPv6Unspecified(), 0), } w.AllowedIPs = gosettings.DefaultSlice(w.AllowedIPs, defaultAllowedIPs) - w.Interface = gosettings.DefaultString(w.Interface, "wg0") + w.Interface = gosettings.DefaultComparable(w.Interface, "wg0") const defaultMTU = 1400 - w.MTU = gosettings.DefaultNumber(w.MTU, defaultMTU) - w.Implementation = gosettings.DefaultString(w.Implementation, "auto") + w.MTU = gosettings.DefaultComparable(w.MTU, defaultMTU) + w.Implementation = gosettings.DefaultComparable(w.Implementation, "auto") } func (w Wireguard) String() string { @@ -222,3 +213,27 @@ func (w Wireguard) toLinesNode() (node *gotree.Node) { return node } + +func (w *Wireguard) read(r *reader.Reader) (err error) { + w.PrivateKey = r.Get("WIREGUARD_PRIVATE_KEY", reader.ForceLowercase(false)) + w.PreSharedKey = r.Get("WIREGUARD_PRESHARED_KEY", reader.ForceLowercase(false)) + w.Interface = r.String("VPN_INTERFACE", + reader.RetroKeys("WIREGUARD_INTERFACE"), reader.ForceLowercase(false)) + w.Implementation = r.String("WIREGUARD_IMPLEMENTATION") + w.Addresses, err = r.CSVNetipPrefixes("WIREGUARD_ADDRESSES", + reader.RetroKeys("WIREGUARD_ADDRESS")) + if err != nil { + return err // already wrapped + } + w.AllowedIPs, err = r.CSVNetipPrefixes("WIREGUARD_ALLOWED_IPS") + if err != nil { + return err // already wrapped + } + mtuPtr, err := r.Uint16Ptr("WIREGUARD_MTU") + if err != nil { + return err + } else if mtuPtr != nil { + w.MTU = *mtuPtr + } + return nil +} diff --git a/internal/configuration/settings/wireguardselection.go b/internal/configuration/settings/wireguardselection.go index bf061f99c..37fd84105 100644 --- a/internal/configuration/settings/wireguardselection.go +++ b/internal/configuration/settings/wireguardselection.go @@ -6,6 +6,7 @@ import ( "github.com/qdm12/gluetun/internal/constants/providers" "github.com/qdm12/gosettings" + "github.com/qdm12/gosettings/reader" "github.com/qdm12/gosettings/validate" "github.com/qdm12/gotree" "golang.zx2c4.com/wireguard/wgctrl/wgtypes" @@ -116,16 +117,10 @@ func (w *WireguardSelection) copy() (copied WireguardSelection) { } } -func (w *WireguardSelection) mergeWith(other WireguardSelection) { - w.EndpointIP = gosettings.MergeWithValidator(w.EndpointIP, other.EndpointIP) - w.EndpointPort = gosettings.MergeWithPointer(w.EndpointPort, other.EndpointPort) - w.PublicKey = gosettings.MergeWithString(w.PublicKey, other.PublicKey) -} - func (w *WireguardSelection) overrideWith(other WireguardSelection) { w.EndpointIP = gosettings.OverrideWithValidator(w.EndpointIP, other.EndpointIP) w.EndpointPort = gosettings.OverrideWithPointer(w.EndpointPort, other.EndpointPort) - w.PublicKey = gosettings.OverrideWithString(w.PublicKey, other.PublicKey) + w.PublicKey = gosettings.OverrideWithComparable(w.PublicKey, other.PublicKey) } func (w *WireguardSelection) setDefaults() { @@ -154,3 +149,18 @@ func (w WireguardSelection) toLinesNode() (node *gotree.Node) { return node } + +func (w *WireguardSelection) read(r *reader.Reader) (err error) { + w.EndpointIP, err = r.NetipAddr("VPN_ENDPOINT_IP", reader.RetroKeys("WIREGUARD_ENDPOINT_IP")) + if err != nil { + return err + } + + w.EndpointPort, err = r.Uint16Ptr("VPN_ENDPOINT_PORT", reader.RetroKeys("WIREGUARD_ENDPOINT_PORT")) + if err != nil { + return err + } + + w.PublicKey = r.String("WIREGUARD_PUBLIC_KEY", reader.ForceLowercase(false)) + return nil +} diff --git a/internal/configuration/sources/env/dns.go b/internal/configuration/sources/env/dns.go deleted file mode 100644 index c340d7fd1..000000000 --- a/internal/configuration/sources/env/dns.go +++ /dev/null @@ -1,55 +0,0 @@ -package env - -import ( - "fmt" - "net/netip" - - "github.com/qdm12/gluetun/internal/configuration/settings" -) - -func (s *Source) readDNS() (dns settings.DNS, err error) { - dns.ServerAddress, err = s.readDNSServerAddress() - if err != nil { - return dns, err - } - - dns.KeepNameserver, err = s.env.BoolPtr("DNS_KEEP_NAMESERVER") - if err != nil { - return dns, err - } - - dns.DoT, err = s.readDoT() - if err != nil { - return dns, fmt.Errorf("DoT settings: %w", err) - } - - return dns, nil -} - -func (s *Source) readDNSServerAddress() (address netip.Addr, err error) { - const currentKey = "DNS_ADDRESS" - key := firstKeySet(s.env, "DNS_PLAINTEXT_ADDRESS", currentKey) - switch key { - case "": - return address, nil - case currentKey: - default: // Retro-compatibility - s.handleDeprecatedKey(key, currentKey) - } - - address, err = s.env.NetipAddr(key) - if err != nil { - return address, err - } - - // TODO remove in v4 - if address.Unmap().Compare(netip.AddrFrom4([4]byte{127, 0, 0, 1})) != 0 { - s.warner.Warn(key + " is set to " + address.String() + - " so the DNS over TLS (DoT) server will not be used." + - " The default value changed to 127.0.0.1 so it uses the internal DoT serves." + - " If the DoT server fails to start, the IPv4 address of the first plaintext DNS server" + - " corresponding to the first DoT provider chosen is used.") - } - - return address, nil -} diff --git a/internal/configuration/sources/env/dnsblacklist.go b/internal/configuration/sources/env/dnsblacklist.go deleted file mode 100644 index 20f445937..000000000 --- a/internal/configuration/sources/env/dnsblacklist.go +++ /dev/null @@ -1,73 +0,0 @@ -package env - -import ( - "errors" - "fmt" - "net/netip" - - "github.com/qdm12/gluetun/internal/configuration/settings" - "github.com/qdm12/gosettings/sources/env" -) - -func (s *Source) readDNSBlacklist() (blacklist settings.DNSBlacklist, err error) { - blacklist.BlockMalicious, err = s.env.BoolPtr("BLOCK_MALICIOUS") - if err != nil { - return blacklist, err - } - - blacklist.BlockSurveillance, err = s.env.BoolPtr("BLOCK_SURVEILLANCE", - env.RetroKeys("BLOCK_NSA")) - if err != nil { - return blacklist, err - } - - blacklist.BlockAds, err = s.env.BoolPtr("BLOCK_ADS") - if err != nil { - return blacklist, err - } - - blacklist.AddBlockedIPs, blacklist.AddBlockedIPPrefixes, - err = s.readDoTPrivateAddresses() // TODO v4 split in 2 - if err != nil { - return blacklist, err - } - - blacklist.AllowedHosts = s.env.CSV("UNBLOCK") // TODO v4 change name - - return blacklist, nil -} - -var ( - ErrPrivateAddressNotValid = errors.New("private address is not a valid IP or CIDR range") -) - -func (s *Source) readDoTPrivateAddresses() (ips []netip.Addr, - ipPrefixes []netip.Prefix, err error) { - privateAddresses := s.env.CSV("DOT_PRIVATE_ADDRESS") - if len(privateAddresses) == 0 { - return nil, nil, nil - } - - ips = make([]netip.Addr, 0, len(privateAddresses)) - ipPrefixes = make([]netip.Prefix, 0, len(privateAddresses)) - - for _, privateAddress := range privateAddresses { - ip, err := netip.ParseAddr(privateAddress) - if err == nil { - ips = append(ips, ip) - continue - } - - ipPrefix, err := netip.ParsePrefix(privateAddress) - if err == nil { - ipPrefixes = append(ipPrefixes, ipPrefix) - continue - } - - return nil, nil, fmt.Errorf( - "environment variable DOT_PRIVATE_ADDRESS: %w: %s", - ErrPrivateAddressNotValid, privateAddress) - } - - return ips, ipPrefixes, nil -} diff --git a/internal/configuration/sources/env/dot.go b/internal/configuration/sources/env/dot.go deleted file mode 100644 index ab9d3cd93..000000000 --- a/internal/configuration/sources/env/dot.go +++ /dev/null @@ -1,29 +0,0 @@ -package env - -import ( - "github.com/qdm12/gluetun/internal/configuration/settings" -) - -func (s *Source) readDoT() (dot settings.DoT, err error) { - dot.Enabled, err = s.env.BoolPtr("DOT") - if err != nil { - return dot, err - } - - dot.UpdatePeriod, err = s.env.DurationPtr("DNS_UPDATE_PERIOD") - if err != nil { - return dot, err - } - - dot.Unbound, err = s.readUnbound() - if err != nil { - return dot, err - } - - dot.Blacklist, err = s.readDNSBlacklist() - if err != nil { - return dot, err - } - - return dot, nil -} diff --git a/internal/configuration/sources/env/firewall.go b/internal/configuration/sources/env/firewall.go deleted file mode 100644 index bcaba9449..000000000 --- a/internal/configuration/sources/env/firewall.go +++ /dev/null @@ -1,36 +0,0 @@ -package env - -import ( - "github.com/qdm12/gluetun/internal/configuration/settings" - "github.com/qdm12/gosettings/sources/env" -) - -func (s *Source) readFirewall() (firewall settings.Firewall, err error) { - firewall.VPNInputPorts, err = s.env.CSVUint16("FIREWALL_VPN_INPUT_PORTS") - if err != nil { - return firewall, err - } - - firewall.InputPorts, err = s.env.CSVUint16("FIREWALL_INPUT_PORTS") - if err != nil { - return firewall, err - } - - firewall.OutboundSubnets, err = s.env.CSVNetipPrefixes("FIREWALL_OUTBOUND_SUBNETS", - env.RetroKeys("EXTRA_SUBNETS")) - if err != nil { - return firewall, err - } - - firewall.Enabled, err = s.env.BoolPtr("FIREWALL") - if err != nil { - return firewall, err - } - - firewall.Debug, err = s.env.BoolPtr("FIREWALL_DEBUG") - if err != nil { - return firewall, err - } - - return firewall, nil -} diff --git a/internal/configuration/sources/env/health.go b/internal/configuration/sources/env/health.go deleted file mode 100644 index 480333730..000000000 --- a/internal/configuration/sources/env/health.go +++ /dev/null @@ -1,35 +0,0 @@ -package env - -import ( - "github.com/qdm12/gluetun/internal/configuration/settings" - "github.com/qdm12/gosettings/sources/env" -) - -func (s *Source) ReadHealth() (health settings.Health, err error) { - health.ServerAddress = s.env.String("HEALTH_SERVER_ADDRESS") - health.TargetAddress = s.env.String("HEALTH_TARGET_ADDRESS", - env.RetroKeys("HEALTH_ADDRESS_TO_PING")) - - successWaitPtr, err := s.env.DurationPtr("HEALTH_SUCCESS_WAIT_DURATION") - if err != nil { - return health, err - } else if successWaitPtr != nil { - health.SuccessWait = *successWaitPtr - } - - health.VPN.Initial, err = s.env.DurationPtr( - "HEALTH_VPN_DURATION_INITIAL", - env.RetroKeys("HEALTH_OPENVPN_DURATION_INITIAL")) - if err != nil { - return health, err - } - - health.VPN.Addition, err = s.env.DurationPtr( - "HEALTH_VPN_DURATION_ADDITION", - env.RetroKeys("HEALTH_OPENVPN_DURATION_ADDITION")) - if err != nil { - return health, err - } - - return health, nil -} diff --git a/internal/configuration/sources/env/helpers.go b/internal/configuration/sources/env/helpers.go deleted file mode 100644 index dc877e008..000000000 --- a/internal/configuration/sources/env/helpers.go +++ /dev/null @@ -1,33 +0,0 @@ -package env - -import ( - "fmt" - "os" - - "github.com/qdm12/gosettings/sources/env" -) - -func unsetEnvKeys(envKeys []string, err error) (newErr error) { - newErr = err - for _, envKey := range envKeys { - unsetErr := os.Unsetenv(envKey) - if unsetErr != nil && newErr == nil { - newErr = fmt.Errorf("unsetting environment variable %s: %w", envKey, unsetErr) - } - } - return newErr -} - -func ptrTo[T any](value T) *T { - return &value -} - -func firstKeySet(e env.Env, keys ...string) (firstKeySet string) { - for _, key := range keys { - value := e.Get(key) - if value != nil { - return key - } - } - return "" -} diff --git a/internal/configuration/sources/env/httproxy.go b/internal/configuration/sources/env/httproxy.go deleted file mode 100644 index 41d7b27ee..000000000 --- a/internal/configuration/sources/env/httproxy.go +++ /dev/null @@ -1,84 +0,0 @@ -package env - -import ( - "fmt" - - "github.com/qdm12/gluetun/internal/configuration/settings" - "github.com/qdm12/gosettings/sources/env" - "github.com/qdm12/govalid/binary" -) - -func (s *Source) readHTTPProxy() (httpProxy settings.HTTPProxy, err error) { - httpProxy.User = s.env.Get("HTTPPROXY_USER", - env.RetroKeys("PROXY_USER", "TINYPROXY_USER"), - env.ForceLowercase(false)) - - httpProxy.Password = s.env.Get("HTTPPROXY_PASSWORD", - env.RetroKeys("PROXY_PASSWORD", "TINYPROXY_PASSWORD"), - env.ForceLowercase(false)) - - httpProxy.ListeningAddress, err = s.readHTTProxyListeningAddress() - if err != nil { - return httpProxy, err - } - - httpProxy.Enabled, err = s.env.BoolPtr("HTTPPROXY", env.RetroKeys("PROXY", "TINYPROXY")) - if err != nil { - return httpProxy, err - } - - httpProxy.Stealth, err = s.env.BoolPtr("HTTPPROXY_STEALTH") - if err != nil { - return httpProxy, err - } - - httpProxy.Log, err = s.readHTTProxyLog() - if err != nil { - return httpProxy, err - } - - return httpProxy, nil -} - -func (s *Source) readHTTProxyListeningAddress() (listeningAddress string, err error) { - const currentKey = "HTTPPROXY_LISTENING_ADDRESS" - key := firstKeySet(s.env, "HTTPPROXY_PORT", "TINYPROXY_PORT", "PROXY_PORT", - currentKey) - switch key { - case "": - return "", nil - case currentKey: - return s.env.String(key), nil - } - - // Retro-compatible keys using a port only - s.handleDeprecatedKey(key, currentKey) - port, err := s.env.Uint16Ptr(key) - if err != nil { - return "", err - } - return fmt.Sprintf(":%d", *port), nil -} - -func (s *Source) readHTTProxyLog() (enabled *bool, err error) { - const currentKey = "HTTPPROXY_LOG" - key := firstKeySet(s.env, "PROXY_LOG", "TINYPROXY_LOG", "HTTPPROXY_LOG") - switch key { - case "": - return nil, nil //nolint:nilnil - case currentKey: - return s.env.BoolPtr(key) - } - - // Retro-compatible keys using different boolean verbs - s.handleDeprecatedKey(key, currentKey) - value := s.env.String(key) - retroOption := binary.OptionEnabled("on", "info", "connect", "notice") - - enabled, err = binary.Validate(value, retroOption) - if err != nil { - return nil, fmt.Errorf("environment variable %s: %w", key, err) - } - - return enabled, nil -} diff --git a/internal/configuration/sources/env/log.go b/internal/configuration/sources/env/log.go deleted file mode 100644 index 9b336dea4..000000000 --- a/internal/configuration/sources/env/log.go +++ /dev/null @@ -1,53 +0,0 @@ -package env - -import ( - "errors" - "fmt" - "strings" - - "github.com/qdm12/gluetun/internal/configuration/settings" - "github.com/qdm12/log" -) - -func (s *Source) readLog() (log settings.Log, err error) { - log.Level, err = s.readLogLevel() - if err != nil { - return log, err - } - - return log, nil -} - -func (s *Source) readLogLevel() (level *log.Level, err error) { - value := s.env.String("LOG_LEVEL") - if value == "" { - return nil, nil //nolint:nilnil - } - - level = new(log.Level) - *level, err = parseLogLevel(value) - if err != nil { - return nil, fmt.Errorf("environment variable LOG_LEVEL: %w", err) - } - - return level, nil -} - -var ErrLogLevelUnknown = errors.New("log level is unknown") - -func parseLogLevel(s string) (level log.Level, err error) { - switch strings.ToLower(s) { - case "debug": - return log.LevelDebug, nil - case "info": - return log.LevelInfo, nil - case "warning": - return log.LevelWarn, nil - case "error": - return log.LevelError, nil - default: - return level, fmt.Errorf( - "%w: %q is not valid and can be one of debug, info, warning or error", - ErrLogLevelUnknown, s) - } -} diff --git a/internal/configuration/sources/env/openvpn.go b/internal/configuration/sources/env/openvpn.go deleted file mode 100644 index 700c95c77..000000000 --- a/internal/configuration/sources/env/openvpn.go +++ /dev/null @@ -1,77 +0,0 @@ -package env - -import ( - "strings" - - "github.com/qdm12/gluetun/internal/configuration/settings" - "github.com/qdm12/gosettings/sources/env" -) - -func (s *Source) readOpenVPN() ( - openVPN settings.OpenVPN, err error) { - defer func() { - err = unsetEnvKeys([]string{"OPENVPN_KEY", "OPENVPN_CERT", - "OPENVPN_KEY_PASSPHRASE", "OPENVPN_ENCRYPTED_KEY"}, err) - }() - - openVPN.Version = s.env.String("OPENVPN_VERSION") - openVPN.User = s.env.Get("OPENVPN_USER", - env.RetroKeys("USER"), env.ForceLowercase(false)) - openVPN.Password = s.env.Get("OPENVPN_PASSWORD", - env.RetroKeys("PASSWORD"), env.ForceLowercase(false)) - openVPN.ConfFile = s.env.Get("OPENVPN_CUSTOM_CONFIG", env.ForceLowercase(false)) - openVPN.Ciphers = s.env.CSV("OPENVPN_CIPHERS", env.RetroKeys("OPENVPN_CIPHER")) - openVPN.Auth = s.env.Get("OPENVPN_AUTH") - openVPN.Cert = s.env.Get("OPENVPN_CERT", env.ForceLowercase(false)) - openVPN.Key = s.env.Get("OPENVPN_KEY", env.ForceLowercase(false)) - openVPN.EncryptedKey = s.env.Get("OPENVPN_ENCRYPTED_KEY", env.ForceLowercase(false)) - openVPN.KeyPassphrase = s.env.Get("OPENVPN_KEY_PASSPHRASE", env.ForceLowercase(false)) - - openVPN.PIAEncPreset = s.readPIAEncryptionPreset() - - openVPN.MSSFix, err = s.env.Uint16Ptr("OPENVPN_MSSFIX") - if err != nil { - return openVPN, err - } - - openVPN.Interface = s.env.String("VPN_INTERFACE", - env.RetroKeys("OPENVPN_INTERFACE"), env.ForceLowercase(false)) - - openVPN.ProcessUser, err = s.readOpenVPNProcessUser() - if err != nil { - return openVPN, err - } - - openVPN.Verbosity, err = s.env.IntPtr("OPENVPN_VERBOSITY") - if err != nil { - return openVPN, err - } - - flagsPtr := s.env.Get("OPENVPN_FLAGS", env.ForceLowercase(false)) - if flagsPtr != nil { - openVPN.Flags = strings.Fields(*flagsPtr) - } - - return openVPN, nil -} - -func (s *Source) readPIAEncryptionPreset() (presetPtr *string) { - return s.env.Get( - "PRIVATE_INTERNET_ACCESS_OPENVPN_ENCRYPTION_PRESET", - env.RetroKeys("ENCRYPTION", "PIA_ENCRYPTION")) -} - -func (s *Source) readOpenVPNProcessUser() (processUser string, err error) { - value, err := s.env.BoolPtr("OPENVPN_ROOT") // Retro-compatibility - if err != nil { - return "", err - } else if value != nil { - if *value { - return "root", nil - } - const defaultNonRootUser = "nonrootuser" - return defaultNonRootUser, nil - } - - return s.env.String("OPENVPN_PROCESS_USER"), nil -} diff --git a/internal/configuration/sources/env/openvpnselection.go b/internal/configuration/sources/env/openvpnselection.go deleted file mode 100644 index e68cf2165..000000000 --- a/internal/configuration/sources/env/openvpnselection.go +++ /dev/null @@ -1,26 +0,0 @@ -package env - -import ( - "github.com/qdm12/gluetun/internal/configuration/settings" - "github.com/qdm12/gosettings/sources/env" -) - -func (s *Source) readOpenVPNSelection() ( - selection settings.OpenVPNSelection, err error) { - selection.ConfFile = s.env.Get("OPENVPN_CUSTOM_CONFIG", env.ForceLowercase(false)) - - selection.Protocol = s.env.String("OPENVPN_PROTOCOL", env.RetroKeys("PROTOCOL")) - if err != nil { - return selection, err - } - - selection.CustomPort, err = s.env.Uint16Ptr("VPN_ENDPOINT_PORT", - env.RetroKeys("PORT", "OPENVPN_PORT")) - if err != nil { - return selection, err - } - - selection.PIAEncPreset = s.readPIAEncryptionPreset() - - return selection, nil -} diff --git a/internal/configuration/sources/env/portforward.go b/internal/configuration/sources/env/portforward.go deleted file mode 100644 index 8651f2e90..000000000 --- a/internal/configuration/sources/env/portforward.go +++ /dev/null @@ -1,34 +0,0 @@ -package env - -import ( - "github.com/qdm12/gluetun/internal/configuration/settings" - "github.com/qdm12/gosettings/sources/env" -) - -func (s *Source) readPortForward() ( - portForwarding settings.PortForwarding, err error) { - portForwarding.Enabled, err = s.env.BoolPtr("VPN_PORT_FORWARDING", - env.RetroKeys( - "PORT_FORWARDING", - "PRIVATE_INTERNET_ACCESS_VPN_PORT_FORWARDING", - )) - if err != nil { - return portForwarding, err - } - - portForwarding.Provider = s.env.Get("VPN_PORT_FORWARDING_PROVIDER") - - portForwarding.Filepath = s.env.Get("VPN_PORT_FORWARDING_STATUS_FILE", - env.ForceLowercase(false), - env.RetroKeys( - "PORT_FORWARDING_STATUS_FILE", - "PRIVATE_INTERNET_ACCESS_VPN_PORT_FORWARDING_STATUS_FILE", - )) - - portForwarding.ListeningPort, err = s.env.Uint16Ptr("VPN_PORT_FORWARDING_LISTENING_PORT") - if err != nil { - return portForwarding, err - } - - return portForwarding, nil -} diff --git a/internal/configuration/sources/env/pprof.go b/internal/configuration/sources/env/pprof.go deleted file mode 100644 index 8f6cf79c2..000000000 --- a/internal/configuration/sources/env/pprof.go +++ /dev/null @@ -1,26 +0,0 @@ -package env - -import ( - "github.com/qdm12/gluetun/internal/pprof" -) - -func (s *Source) readPprof() (settings pprof.Settings, err error) { - settings.Enabled, err = s.env.BoolPtr("PPROF_ENABLED") - if err != nil { - return settings, err - } - - settings.BlockProfileRate, err = s.env.IntPtr("PPROF_BLOCK_PROFILE_RATE") - if err != nil { - return settings, err - } - - settings.MutexProfileRate, err = s.env.IntPtr("PPROF_MUTEX_PROFILE_RATE") - if err != nil { - return settings, err - } - - settings.HTTPServer.Address = s.env.String("PPROF_HTTP_SERVER_ADDRESS") - - return settings, nil -} diff --git a/internal/configuration/sources/env/provider.go b/internal/configuration/sources/env/provider.go deleted file mode 100644 index 9a52b835e..000000000 --- a/internal/configuration/sources/env/provider.go +++ /dev/null @@ -1,45 +0,0 @@ -package env - -import ( - "fmt" - "strings" - - "github.com/qdm12/gluetun/internal/configuration/settings" - "github.com/qdm12/gluetun/internal/constants/providers" - "github.com/qdm12/gluetun/internal/constants/vpn" - "github.com/qdm12/gosettings/sources/env" -) - -func (s *Source) readProvider(vpnType string) (provider settings.Provider, err error) { - provider.Name = s.readVPNServiceProvider(vpnType) - - provider.ServerSelection, err = s.readServerSelection(provider.Name, vpnType) - if err != nil { - return provider, fmt.Errorf("server selection: %w", err) - } - - provider.PortForwarding, err = s.readPortForward() - if err != nil { - return provider, fmt.Errorf("port forwarding: %w", err) - } - - return provider, nil -} - -func (s *Source) readVPNServiceProvider(vpnType string) (vpnProvider string) { - vpnProvider = s.env.String("VPN_SERVICE_PROVIDER", env.RetroKeys("VPNSP")) - if vpnProvider == "" { - if vpnType != vpn.Wireguard && s.env.Get("OPENVPN_CUSTOM_CONFIG") != nil { - // retro compatibility - return providers.Custom - } - return "" - } - - vpnProvider = strings.ToLower(vpnProvider) - if vpnProvider == "pia" { // retro compatibility - return providers.PrivateInternetAccess - } - - return vpnProvider -} diff --git a/internal/configuration/sources/env/publicip.go b/internal/configuration/sources/env/publicip.go deleted file mode 100644 index 934de70a2..000000000 --- a/internal/configuration/sources/env/publicip.go +++ /dev/null @@ -1,22 +0,0 @@ -package env - -import ( - "github.com/qdm12/gluetun/internal/configuration/settings" - "github.com/qdm12/gosettings/sources/env" -) - -func (s *Source) readPublicIP() (publicIP settings.PublicIP, err error) { - publicIP.Period, err = s.env.DurationPtr("PUBLICIP_PERIOD") - if err != nil { - return publicIP, err - } - - publicIP.IPFilepath = s.env.Get("PUBLICIP_FILE", - env.ForceLowercase(false), env.RetroKeys("IP_STATUS_FILE")) - - publicIP.API = s.env.String("PUBLICIP_API") - - publicIP.APIToken = s.env.Get("PUBLICIP_API_TOKEN") - - return publicIP, nil -} diff --git a/internal/configuration/sources/env/reader.go b/internal/configuration/sources/env/reader.go deleted file mode 100644 index a49439f84..000000000 --- a/internal/configuration/sources/env/reader.go +++ /dev/null @@ -1,103 +0,0 @@ -package env - -import ( - "os" - - "github.com/qdm12/gluetun/internal/configuration/settings" - "github.com/qdm12/gosettings/sources/env" -) - -type Source struct { - env env.Env - warner Warner - handleDeprecatedKey func(deprecatedKey, newKey string) -} - -type Warner interface { - Warn(s string) -} - -func New(warner Warner) *Source { - handleDeprecatedKey := func(deprecatedKey, newKey string) { - warner.Warn( - "You are using the old environment variable " + deprecatedKey + - ", please consider changing it to " + newKey) - } - - return &Source{ - env: *env.New(os.Environ(), handleDeprecatedKey), - warner: warner, - handleDeprecatedKey: handleDeprecatedKey, - } -} - -func (s *Source) String() string { return "environment variables" } - -func (s *Source) Read() (settings settings.Settings, err error) { - settings.VPN, err = s.readVPN() - if err != nil { - return settings, err - } - - settings.Firewall, err = s.readFirewall() - if err != nil { - return settings, err - } - - settings.System, err = s.readSystem() - if err != nil { - return settings, err - } - - settings.Health, err = s.ReadHealth() - if err != nil { - return settings, err - } - - settings.HTTPProxy, err = s.readHTTPProxy() - if err != nil { - return settings, err - } - - settings.Log, err = s.readLog() - if err != nil { - return settings, err - } - - settings.PublicIP, err = s.readPublicIP() - if err != nil { - return settings, err - } - - settings.Updater, err = s.readUpdater() - if err != nil { - return settings, err - } - - settings.Version, err = s.readVersion() - if err != nil { - return settings, err - } - - settings.Shadowsocks, err = s.readShadowsocks() - if err != nil { - return settings, err - } - - settings.DNS, err = s.readDNS() - if err != nil { - return settings, err - } - - settings.ControlServer, err = s.readControlServer() - if err != nil { - return settings, err - } - - settings.Pprof, err = s.readPprof() - if err != nil { - return settings, err - } - - return settings, nil -} diff --git a/internal/configuration/sources/env/server.go b/internal/configuration/sources/env/server.go deleted file mode 100644 index 3064f1507..000000000 --- a/internal/configuration/sources/env/server.go +++ /dev/null @@ -1,16 +0,0 @@ -package env - -import ( - "github.com/qdm12/gluetun/internal/configuration/settings" -) - -func (s *Source) readControlServer() (controlServer settings.ControlServer, err error) { - controlServer.Log, err = s.env.BoolPtr("HTTP_CONTROL_SERVER_LOG") - if err != nil { - return controlServer, err - } - - controlServer.Address = s.env.Get("HTTP_CONTROL_SERVER_ADDRESS") - - return controlServer, nil -} diff --git a/internal/configuration/sources/env/serverselection.go b/internal/configuration/sources/env/serverselection.go deleted file mode 100644 index a86abe8c3..000000000 --- a/internal/configuration/sources/env/serverselection.go +++ /dev/null @@ -1,92 +0,0 @@ -package env - -import ( - "errors" - - "github.com/qdm12/gluetun/internal/configuration/settings" - "github.com/qdm12/gluetun/internal/constants/providers" - "github.com/qdm12/gosettings/sources/env" -) - -func (s *Source) readServerSelection(vpnProvider, vpnType string) ( - ss settings.ServerSelection, err error) { - ss.VPN = vpnType - - ss.TargetIP, err = s.env.NetipAddr("VPN_ENDPOINT_IP", - env.RetroKeys("OPENVPN_TARGET_IP")) - if err != nil { - return ss, err - } - - ss.Countries = s.env.CSV("SERVER_COUNTRIES", env.RetroKeys("COUNTRY")) - if vpnProvider == providers.Cyberghost && len(ss.Countries) == 0 { - // Retro-compatibility for Cyberghost using the REGION variable - ss.Countries = s.env.CSV("REGION") - if len(ss.Countries) > 0 { - s.handleDeprecatedKey("REGION", "SERVER_COUNTRIES") - } - } - - ss.Regions = s.env.CSV("SERVER_REGIONS", env.RetroKeys("REGION")) - ss.Cities = s.env.CSV("SERVER_CITIES", env.RetroKeys("CITY")) - ss.ISPs = s.env.CSV("ISP") - ss.Hostnames = s.env.CSV("SERVER_HOSTNAMES", env.RetroKeys("SERVER_HOSTNAME")) - ss.Names = s.env.CSV("SERVER_NAMES", env.RetroKeys("SERVER_NAME")) - ss.Numbers, err = s.env.CSVUint16("SERVER_NUMBER") - ss.Categories = s.env.CSV("SERVER_CATEGORIES") - if err != nil { - return ss, err - } - - // Mullvad only - ss.OwnedOnly, err = s.env.BoolPtr("OWNED_ONLY", env.RetroKeys("OWNED")) - if err != nil { - return ss, err - } - - // VPNUnlimited and ProtonVPN only - ss.FreeOnly, err = s.env.BoolPtr("FREE_ONLY") - if err != nil { - return ss, err - } - - // VPNSecure only - ss.PremiumOnly, err = s.env.BoolPtr("PREMIUM_ONLY") - if err != nil { - return ss, err - } - - // Surfshark only - ss.MultiHopOnly, err = s.env.BoolPtr("MULTIHOP_ONLY") - if err != nil { - return ss, err - } - - // VPNUnlimited only - ss.StreamOnly, err = s.env.BoolPtr("STREAM_ONLY") - if err != nil { - return ss, err - } - - // PIA only - ss.PortForwardOnly, err = s.env.BoolPtr("PORT_FORWARD_ONLY") - if err != nil { - return ss, err - } - - ss.OpenVPN, err = s.readOpenVPNSelection() - if err != nil { - return ss, err - } - - ss.Wireguard, err = s.readWireguardSelection() - if err != nil { - return ss, err - } - - return ss, nil -} - -var ( - ErrInvalidIP = errors.New("invalid IP address") -) diff --git a/internal/configuration/sources/env/shadowsocks.go b/internal/configuration/sources/env/shadowsocks.go deleted file mode 100644 index 8b6123742..000000000 --- a/internal/configuration/sources/env/shadowsocks.go +++ /dev/null @@ -1,42 +0,0 @@ -package env - -import ( - "fmt" - - "github.com/qdm12/gluetun/internal/configuration/settings" - "github.com/qdm12/gosettings/sources/env" -) - -func (s *Source) readShadowsocks() (shadowsocks settings.Shadowsocks, err error) { - shadowsocks.Enabled, err = s.env.BoolPtr("SHADOWSOCKS") - if err != nil { - return shadowsocks, err - } - - shadowsocks.Address, err = s.readShadowsocksAddress() - if err != nil { - return shadowsocks, err - } - shadowsocks.LogAddresses, err = s.env.BoolPtr("SHADOWSOCKS_LOG") - if err != nil { - return shadowsocks, err - } - shadowsocks.CipherName = s.env.String("SHADOWSOCKS_CIPHER", - env.RetroKeys("SHADOWSOCKS_METHOD")) - shadowsocks.Password = s.env.Get("SHADOWSOCKS_PASSWORD", env.ForceLowercase(false)) - - return shadowsocks, nil -} - -func (s *Source) readShadowsocksAddress() (address *string, err error) { - const currentKey = "SHADOWSOCKS_LISTENING_ADDRESS" - port, err := s.env.Uint16Ptr("SHADOWSOCKS_PORT") // retro-compatibility - if err != nil { - return nil, err - } else if port != nil { - s.handleDeprecatedKey("SHADOWSOCKS_PORT", currentKey) - return ptrTo(fmt.Sprintf(":%d", *port)), nil - } - - return s.env.Get(currentKey), nil -} diff --git a/internal/configuration/sources/env/system.go b/internal/configuration/sources/env/system.go deleted file mode 100644 index 3c5d9490e..000000000 --- a/internal/configuration/sources/env/system.go +++ /dev/null @@ -1,22 +0,0 @@ -package env - -import ( - "github.com/qdm12/gluetun/internal/configuration/settings" - "github.com/qdm12/gosettings/sources/env" -) - -func (s *Source) readSystem() (system settings.System, err error) { - system.PUID, err = s.env.Uint32Ptr("PUID", env.RetroKeys("UID")) - if err != nil { - return system, err - } - - system.PGID, err = s.env.Uint32Ptr("PGID", env.RetroKeys("GID")) - if err != nil { - return system, err - } - - system.Timezone = s.env.String("TZ") - - return system, nil -} diff --git a/internal/configuration/sources/env/unbound.go b/internal/configuration/sources/env/unbound.go deleted file mode 100644 index efe113286..000000000 --- a/internal/configuration/sources/env/unbound.go +++ /dev/null @@ -1,36 +0,0 @@ -package env - -import ( - "github.com/qdm12/gluetun/internal/configuration/settings" -) - -func (s *Source) readUnbound() (unbound settings.Unbound, err error) { - unbound.Providers = s.env.CSV("DOT_PROVIDERS") - - unbound.Caching, err = s.env.BoolPtr("DOT_CACHING") - if err != nil { - return unbound, err - } - - unbound.IPv6, err = s.env.BoolPtr("DOT_IPV6") - if err != nil { - return unbound, err - } - - unbound.VerbosityLevel, err = s.env.Uint8Ptr("DOT_VERBOSITY") - if err != nil { - return unbound, err - } - - unbound.VerbosityDetailsLevel, err = s.env.Uint8Ptr("DOT_VERBOSITY_DETAILS") - if err != nil { - return unbound, err - } - - unbound.ValidationLogLevel, err = s.env.Uint8Ptr("DOT_VALIDATION_LOGLEVEL") - if err != nil { - return unbound, err - } - - return unbound, nil -} diff --git a/internal/configuration/sources/env/updater.go b/internal/configuration/sources/env/updater.go deleted file mode 100644 index 19612e78a..000000000 --- a/internal/configuration/sources/env/updater.go +++ /dev/null @@ -1,35 +0,0 @@ -package env - -import ( - "github.com/qdm12/gluetun/internal/configuration/settings" -) - -func (s *Source) readUpdater() (updater settings.Updater, err error) { - updater.Period, err = s.env.DurationPtr("UPDATER_PERIOD") - if err != nil { - return updater, err - } - - updater.DNSAddress, err = readUpdaterDNSAddress() - if err != nil { - return updater, err - } - - updater.MinRatio, err = s.env.Float64("UPDATER_MIN_RATIO") - if err != nil { - return updater, err - } - - updater.Providers = s.env.CSV("UPDATER_VPN_SERVICE_PROVIDERS") - - return updater, nil -} - -func readUpdaterDNSAddress() (address string, err error) { - // TODO this is currently using Cloudflare in - // plaintext to not be blocked by DNS over TLS by default. - // If a plaintext address is set in the DNS settings, this one will be used. - // use custom future encrypted DNS written in Go without blocking - // as it's too much trouble to start another parallel unbound instance for now. - return "", nil -} diff --git a/internal/configuration/sources/env/version.go b/internal/configuration/sources/env/version.go deleted file mode 100644 index 2e46df03c..000000000 --- a/internal/configuration/sources/env/version.go +++ /dev/null @@ -1,14 +0,0 @@ -package env - -import ( - "github.com/qdm12/gluetun/internal/configuration/settings" -) - -func (s *Source) readVersion() (version settings.Version, err error) { - version.Enabled, err = s.env.BoolPtr("VERSION_INFORMATION") - if err != nil { - return version, err - } - - return version, nil -} diff --git a/internal/configuration/sources/env/vpn.go b/internal/configuration/sources/env/vpn.go deleted file mode 100644 index 00dfec98f..000000000 --- a/internal/configuration/sources/env/vpn.go +++ /dev/null @@ -1,28 +0,0 @@ -package env - -import ( - "fmt" - - "github.com/qdm12/gluetun/internal/configuration/settings" -) - -func (s *Source) readVPN() (vpn settings.VPN, err error) { - vpn.Type = s.env.String("VPN_TYPE") - - vpn.Provider, err = s.readProvider(vpn.Type) - if err != nil { - return vpn, fmt.Errorf("VPN provider: %w", err) - } - - vpn.OpenVPN, err = s.readOpenVPN() - if err != nil { - return vpn, fmt.Errorf("OpenVPN: %w", err) - } - - vpn.Wireguard, err = s.readWireguard() - if err != nil { - return vpn, fmt.Errorf("wireguard: %w", err) - } - - return vpn, nil -} diff --git a/internal/configuration/sources/env/wireguard.go b/internal/configuration/sources/env/wireguard.go deleted file mode 100644 index 20694afa8..000000000 --- a/internal/configuration/sources/env/wireguard.go +++ /dev/null @@ -1,33 +0,0 @@ -package env - -import ( - "github.com/qdm12/gluetun/internal/configuration/settings" - "github.com/qdm12/gosettings/sources/env" -) - -func (s *Source) readWireguard() (wireguard settings.Wireguard, err error) { - defer func() { - err = unsetEnvKeys([]string{"WIREGUARD_PRIVATE_KEY", "WIREGUARD_PRESHARED_KEY"}, err) - }() - wireguard.PrivateKey = s.env.Get("WIREGUARD_PRIVATE_KEY", env.ForceLowercase(false)) - wireguard.PreSharedKey = s.env.Get("WIREGUARD_PRESHARED_KEY", env.ForceLowercase(false)) - wireguard.Interface = s.env.String("VPN_INTERFACE", - env.RetroKeys("WIREGUARD_INTERFACE"), env.ForceLowercase(false)) - wireguard.Implementation = s.env.String("WIREGUARD_IMPLEMENTATION") - wireguard.Addresses, err = s.env.CSVNetipPrefixes("WIREGUARD_ADDRESSES", - env.RetroKeys("WIREGUARD_ADDRESS")) - if err != nil { - return wireguard, err // already wrapped - } - wireguard.AllowedIPs, err = s.env.CSVNetipPrefixes("WIREGUARD_ALLOWED_IPS") - if err != nil { - return wireguard, err // already wrapped - } - mtuPtr, err := s.env.Uint16Ptr("WIREGUARD_MTU") - if err != nil { - return wireguard, err - } else if mtuPtr != nil { - wireguard.MTU = *mtuPtr - } - return wireguard, nil -} diff --git a/internal/configuration/sources/env/wireguardselection.go b/internal/configuration/sources/env/wireguardselection.go deleted file mode 100644 index ebc3a9de1..000000000 --- a/internal/configuration/sources/env/wireguardselection.go +++ /dev/null @@ -1,23 +0,0 @@ -package env - -import ( - "github.com/qdm12/gluetun/internal/configuration/settings" - "github.com/qdm12/gosettings/sources/env" -) - -func (s *Source) readWireguardSelection() ( - selection settings.WireguardSelection, err error) { - selection.EndpointIP, err = s.env.NetipAddr("VPN_ENDPOINT_IP", env.RetroKeys("WIREGUARD_ENDPOINT_IP")) - if err != nil { - return selection, err - } - - selection.EndpointPort, err = s.env.Uint16Ptr("VPN_ENDPOINT_PORT", env.RetroKeys("WIREGUARD_ENDPOINT_PORT")) - if err != nil { - return selection, err - } - - selection.PublicKey = s.env.String("WIREGUARD_PUBLIC_KEY", env.ForceLowercase(false)) - - return selection, nil -} diff --git a/internal/configuration/sources/files/health.go b/internal/configuration/sources/files/health.go deleted file mode 100644 index 11950a4a0..000000000 --- a/internal/configuration/sources/files/health.go +++ /dev/null @@ -1,5 +0,0 @@ -package files - -import "github.com/qdm12/gluetun/internal/configuration/settings" - -func (s *Source) ReadHealth() (settings settings.Health, err error) { return settings, nil } diff --git a/internal/configuration/sources/files/helpers.go b/internal/configuration/sources/files/helpers.go index 83c5842f5..e291885d3 100644 --- a/internal/configuration/sources/files/helpers.go +++ b/internal/configuration/sources/files/helpers.go @@ -9,47 +9,45 @@ import ( "github.com/qdm12/gluetun/internal/openvpn/extract" ) -// ReadFromFile reads the content of the file as a string. -// It returns a nil string pointer if the file does not exist. -func ReadFromFile(filepath string) (s *string, err error) { +// ReadFromFile reads the content of the file as a string, +// and returns if the file was present or not with isSet. +func ReadFromFile(filepath string) (content string, isSet bool, err error) { file, err := os.Open(filepath) if err != nil { if os.IsNotExist(err) { - return nil, nil //nolint:nilnil + return "", false, nil } - return nil, err + return "", false, fmt.Errorf("opening file: %w", err) } b, err := io.ReadAll(file) if err != nil { _ = file.Close() - return nil, err + return "", false, fmt.Errorf("reading file: %w", err) } if err := file.Close(); err != nil { - return nil, err + return "", false, fmt.Errorf("closing file: %w", err) } - content := string(b) + content = string(b) content = strings.TrimSuffix(content, "\r\n") content = strings.TrimSuffix(content, "\n") - return &content, nil + return content, true, nil } -func readPEMFile(filepath string) (base64Ptr *string, err error) { - pemData, err := ReadFromFile(filepath) +func ReadPEMFile(filepath string) (base64Str string, isSet bool, err error) { + pemData, isSet, err := ReadFromFile(filepath) if err != nil { - return nil, fmt.Errorf("reading file: %w", err) + return "", false, fmt.Errorf("reading file: %w", err) + } else if !isSet { + return "", false, nil } - if pemData == nil { - return nil, nil //nolint:nilnil - } - - base64Data, err := extract.PEM([]byte(*pemData)) + base64Str, err = extract.PEM([]byte(pemData)) if err != nil { - return nil, fmt.Errorf("extracting base64 encoded data from PEM content: %w", err) + return "", false, fmt.Errorf("extracting base64 encoded data from PEM content: %w", err) } - return &base64Data, nil + return base64Str, true, nil } diff --git a/internal/configuration/sources/files/helpers_test.go b/internal/configuration/sources/files/helpers_test.go deleted file mode 100644 index a2658d7d8..000000000 --- a/internal/configuration/sources/files/helpers_test.go +++ /dev/null @@ -1,3 +0,0 @@ -package files - -func ptrTo[T any](x T) *T { return &x } diff --git a/internal/configuration/sources/files/interfaces.go b/internal/configuration/sources/files/interfaces.go new file mode 100644 index 000000000..b121e0cca --- /dev/null +++ b/internal/configuration/sources/files/interfaces.go @@ -0,0 +1,5 @@ +package files + +type Warner interface { + Warnf(format string, a ...interface{}) +} diff --git a/internal/configuration/sources/files/openvpn.go b/internal/configuration/sources/files/openvpn.go deleted file mode 100644 index 5c6b2fe96..000000000 --- a/internal/configuration/sources/files/openvpn.go +++ /dev/null @@ -1,33 +0,0 @@ -package files - -import ( - "fmt" - - "github.com/qdm12/gluetun/internal/configuration/settings" -) - -const ( - // OpenVPNClientKeyPath is the OpenVPN client key filepath. - OpenVPNClientKeyPath = "/gluetun/client.key" - // OpenVPNClientCertificatePath is the OpenVPN client certificate filepath. - OpenVPNClientCertificatePath = "/gluetun/client.crt" - openVPNEncryptedKey = "/gluetun/openvpn_encrypted_key" -) - -func (s *Source) readOpenVPN() (settings settings.OpenVPN, err error) { - settings.Key, err = readPEMFile(OpenVPNClientKeyPath) - if err != nil { - return settings, fmt.Errorf("client key: %w", err) - } - - settings.Cert, err = readPEMFile(OpenVPNClientCertificatePath) - if err != nil { - return settings, fmt.Errorf("client certificate: %w", err) - } - settings.EncryptedKey, err = readPEMFile(openVPNEncryptedKey) - if err != nil { - return settings, fmt.Errorf("reading encrypted key file: %w", err) - } - - return settings, nil -} diff --git a/internal/configuration/sources/files/provider.go b/internal/configuration/sources/files/provider.go deleted file mode 100644 index 5fd2cbe69..000000000 --- a/internal/configuration/sources/files/provider.go +++ /dev/null @@ -1,16 +0,0 @@ -package files - -import ( - "fmt" - - "github.com/qdm12/gluetun/internal/configuration/settings" -) - -func (s *Source) readProvider() (provider settings.Provider, err error) { - provider.ServerSelection, err = s.readServerSelection() - if err != nil { - return provider, fmt.Errorf("server selection: %w", err) - } - - return provider, nil -} diff --git a/internal/configuration/sources/files/reader.go b/internal/configuration/sources/files/reader.go index 378b58053..c50429635 100644 --- a/internal/configuration/sources/files/reader.go +++ b/internal/configuration/sources/files/reader.go @@ -1,32 +1,99 @@ package files import ( - "github.com/qdm12/gluetun/internal/configuration/settings" + "os" + "path/filepath" + "strings" ) type Source struct { - wireguardConfigPath string + rootDirectory string + environ map[string]string + warner Warner + cached struct { + wireguardLoaded bool + wireguardConf WireguardConfig + } } -func New() *Source { - const wireguardConfigPath = "/gluetun/wireguard/wg0.conf" +func New(warner Warner) (source *Source) { + osEnviron := os.Environ() + environ := make(map[string]string, len(osEnviron)) + for _, pair := range osEnviron { + const maxSplit = 2 + split := strings.SplitN(pair, "=", maxSplit) + environ[split[0]] = split[1] + } + return &Source{ - wireguardConfigPath: wireguardConfigPath, + rootDirectory: "/gluetun", + environ: environ, + warner: warner, } } func (s *Source) String() string { return "files" } -func (s *Source) Read() (settings settings.Settings, err error) { - settings.VPN, err = s.readVPN() - if err != nil { - return settings, err +func (s *Source) Get(key string) (value string, isSet bool) { + if key == "" { + return "", false + } + // TODO v4 custom environment variable to set the files parent directory + // and not to set each file to a specific path + envKey := strings.ToUpper(key) + envKey = strings.ReplaceAll(envKey, "-", "_") + envKey += "_FILE" + path := s.environ[envKey] + if path == "" { + path = filepath.Join(s.rootDirectory, key) + } + + // Special file handling + switch key { + // TODO timezone from /etc/localtime + case "client.crt", "client.key": + value, isSet, err := ReadPEMFile(path) + if err != nil { + s.warner.Warnf("skipping %s: parsing PEM: %s", path, err) + } + return value, isSet + case "wireguard_private_key": + return strPtrToStringIsSet(s.lazyLoadWireguardConf().PrivateKey) + case "wireguard_preshared_key": + return strPtrToStringIsSet(s.lazyLoadWireguardConf().PreSharedKey) + case "wireguard_addresses": + return strPtrToStringIsSet(s.lazyLoadWireguardConf().Addresses) + case "wireguard_public_key": + return strPtrToStringIsSet(s.lazyLoadWireguardConf().PublicKey) + case "vpn_endpoint_ip": + return strPtrToStringIsSet(s.lazyLoadWireguardConf().EndpointIP) + case "vpn_endpoint_port": + return strPtrToStringIsSet(s.lazyLoadWireguardConf().EndpointPort) } - settings.System, err = s.readSystem() + value, isSet, err := ReadFromFile(path) if err != nil { - return settings, err + s.warner.Warnf("skipping %s: reading file: %s", path, err) + } + return value, isSet +} + +func (s *Source) KeyTransform(key string) string { + switch key { + // TODO v4 remove these irregular cases + case "OPENVPN_KEY": + return "client.key" + case "OPENVPN_CERT": + return "client.crt" + default: + key = strings.ToLower(key) // HTTPROXY_USER -> httpproxy_user + return key } +} - return settings, nil +func strPtrToStringIsSet(ptr *string) (s string, isSet bool) { + if ptr == nil { + return "", false + } + return *ptr, true } diff --git a/internal/configuration/sources/files/serverselection.go b/internal/configuration/sources/files/serverselection.go deleted file mode 100644 index 89f0cd4c0..000000000 --- a/internal/configuration/sources/files/serverselection.go +++ /dev/null @@ -1,16 +0,0 @@ -package files - -import ( - "fmt" - - "github.com/qdm12/gluetun/internal/configuration/settings" -) - -func (s *Source) readServerSelection() (selection settings.ServerSelection, err error) { - selection.Wireguard, err = s.readWireguardSelection() - if err != nil { - return selection, fmt.Errorf("wireguard: %w", err) - } - - return selection, nil -} diff --git a/internal/configuration/sources/files/system.go b/internal/configuration/sources/files/system.go deleted file mode 100644 index 8f01ba1b7..000000000 --- a/internal/configuration/sources/files/system.go +++ /dev/null @@ -1,10 +0,0 @@ -package files - -import ( - "github.com/qdm12/gluetun/internal/configuration/settings" -) - -func (s *Source) readSystem() (system settings.System, err error) { - // TODO timezone from /etc/localtime - return system, nil -} diff --git a/internal/configuration/sources/files/vpn.go b/internal/configuration/sources/files/vpn.go deleted file mode 100644 index 12d1425f9..000000000 --- a/internal/configuration/sources/files/vpn.go +++ /dev/null @@ -1,26 +0,0 @@ -package files - -import ( - "fmt" - - "github.com/qdm12/gluetun/internal/configuration/settings" -) - -func (s *Source) readVPN() (vpn settings.VPN, err error) { - vpn.Provider, err = s.readProvider() - if err != nil { - return vpn, fmt.Errorf("provider: %w", err) - } - - vpn.OpenVPN, err = s.readOpenVPN() - if err != nil { - return vpn, fmt.Errorf("OpenVPN: %w", err) - } - - vpn.Wireguard, err = s.readWireguard() - if err != nil { - return vpn, fmt.Errorf("wireguard: %w", err) - } - - return vpn, nil -} diff --git a/internal/configuration/sources/files/wireguard.go b/internal/configuration/sources/files/wireguard.go index 69b59e118..49ac9cef3 100644 --- a/internal/configuration/sources/files/wireguard.go +++ b/internal/configuration/sources/files/wireguard.go @@ -1,124 +1,115 @@ package files import ( + "errors" "fmt" - "net/netip" + "os" + "path/filepath" "regexp" "strings" - "github.com/qdm12/gluetun/internal/configuration/settings" - "golang.zx2c4.com/wireguard/wgctrl/wgtypes" "gopkg.in/ini.v1" ) -func (s *Source) readWireguard() (wireguard settings.Wireguard, err error) { - fileStringPtr, err := ReadFromFile(s.wireguardConfigPath) - if err != nil { - return wireguard, fmt.Errorf("reading file: %w", err) +func (s *Source) lazyLoadWireguardConf() WireguardConfig { + if s.cached.wireguardLoaded { + return s.cached.wireguardConf } - if fileStringPtr == nil { - return wireguard, nil + s.cached.wireguardLoaded = true + var err error + s.cached.wireguardConf, err = ParseWireguardConf(filepath.Join(s.rootDirectory, "wg0.conf")) + if err != nil { + s.warner.Warnf("skipping Wireguard config: %s", err) } + return s.cached.wireguardConf +} - rawData := []byte(*fileStringPtr) - return ParseWireguardConf(rawData) +type WireguardConfig struct { + PrivateKey *string + PreSharedKey *string + Addresses *string + PublicKey *string + EndpointIP *string + EndpointPort *string } var ( regexINISectionNotExist = regexp.MustCompile(`^section ".+" does not exist$`) - regexINIKeyNotExist = regexp.MustCompile(`key ".*" not exists$`) ) -func ParseWireguardConf(rawData []byte) (wireguard settings.Wireguard, err error) { - iniFile, err := ini.Load(rawData) +func ParseWireguardConf(path string) (config WireguardConfig, err error) { + iniFile, err := ini.Load(path) if err != nil { - return wireguard, fmt.Errorf("loading ini from reader: %w", err) + if errors.Is(err, os.ErrNotExist) { + return WireguardConfig{}, nil + } + return WireguardConfig{}, fmt.Errorf("loading ini from reader: %w", err) } interfaceSection, err := iniFile.GetSection("Interface") if err == nil { - err = parseWireguardInterfaceSection(interfaceSection, &wireguard) - if err != nil { - return wireguard, fmt.Errorf("parsing interface section: %w", err) - } + config.PrivateKey, config.Addresses = parseWireguardInterfaceSection(interfaceSection) } else if !regexINISectionNotExist.MatchString(err.Error()) { // can never happen - return wireguard, fmt.Errorf("getting interface section: %w", err) + return WireguardConfig{}, fmt.Errorf("getting interface section: %w", err) } peerSection, err := iniFile.GetSection("Peer") if err == nil { - wireguard.PreSharedKey, err = parseINIWireguardKey(peerSection, "PresharedKey") - if err != nil { - return wireguard, fmt.Errorf("parsing peer section: %w", err) - } + config.PreSharedKey, config.PublicKey, config.EndpointIP, + config.EndpointPort = parseWireguardPeerSection(peerSection) } else if !regexINISectionNotExist.MatchString(err.Error()) { // can never happen - return wireguard, fmt.Errorf("getting peer section: %w", err) + return WireguardConfig{}, fmt.Errorf("getting peer section: %w", err) } - return wireguard, nil + return config, nil } -func parseWireguardInterfaceSection(interfaceSection *ini.Section, - wireguard *settings.Wireguard) (err error) { - wireguard.PrivateKey, err = parseINIWireguardKey(interfaceSection, "PrivateKey") - if err != nil { - return err // error is already wrapped correctly - } - - wireguard.Addresses, err = parseINIWireguardAddress(interfaceSection) - if err != nil { - return err // error is already wrapped correctly - } - - return nil +func parseWireguardInterfaceSection(interfaceSection *ini.Section) ( + privateKey, addresses *string) { + privateKey = getINIKeyFromSection(interfaceSection, "PrivateKey") + addresses = getINIKeyFromSection(interfaceSection, "Address") + return privateKey, addresses } -func parseINIWireguardKey(section *ini.Section, keyName string) ( - key *string, err error) { - iniKey, err := section.GetKey(keyName) - if err != nil { - if regexINIKeyNotExist.MatchString(err.Error()) { - return nil, nil //nolint:nilnil +var ( + ErrEndpointHostNotIP = errors.New("endpoint host is not an IP") +) + +func parseWireguardPeerSection(peerSection *ini.Section) ( + preSharedKey, publicKey, endpointIP, endpointPort *string) { + preSharedKey = getINIKeyFromSection(peerSection, "PresharedKey") + publicKey = getINIKeyFromSection(peerSection, "PublicKey") + endpoint := getINIKeyFromSection(peerSection, "Endpoint") + if endpoint != nil { + parts := strings.Split(*endpoint, ":") + endpointIP = &parts[0] + const partsWithPort = 2 + if len(parts) >= partsWithPort { + endpointPort = new(string) + *endpointPort = strings.Join(parts[1:], ":") } - // can never happen - return nil, fmt.Errorf("getting %s key: %w", keyName, err) } - key = new(string) - *key = iniKey.String() - _, err = wgtypes.ParseKey(*key) - if err != nil { - return nil, fmt.Errorf("parsing %s: %s: %w", keyName, *key, err) - } - return key, nil + return preSharedKey, publicKey, endpointIP, endpointPort } -func parseINIWireguardAddress(section *ini.Section) ( - addresses []netip.Prefix, err error) { - addressKey, err := section.GetKey("Address") +var ( + regexINIKeyNotExist = regexp.MustCompile(`key ".*" not exists$`) +) + +func getINIKeyFromSection(section *ini.Section, key string) (value *string) { + iniKey, err := section.GetKey(key) if err != nil { if regexINIKeyNotExist.MatchString(err.Error()) { - return nil, nil + return nil } // can never happen - return nil, fmt.Errorf("getting Address key: %w", err) + panic(fmt.Sprintf("getting key %q: %s", key, err)) } - - addressStrings := strings.Split(addressKey.String(), ",") - addresses = make([]netip.Prefix, len(addressStrings)) - for i, addressString := range addressStrings { - addressString = strings.TrimSpace(addressString) - if !strings.ContainsRune(addressString, '/') { - addressString += "/32" - } - addresses[i], err = netip.ParsePrefix(addressString) - if err != nil { - return nil, fmt.Errorf("parsing address: %w", err) - } - } - - return addresses, nil + value = new(string) + *value = iniKey.String() + return value } diff --git a/internal/configuration/sources/files/wireguard_test.go b/internal/configuration/sources/files/wireguard_test.go index 7236e2f33..732408813 100644 --- a/internal/configuration/sources/files/wireguard_test.go +++ b/internal/configuration/sources/files/wireguard_test.go @@ -1,48 +1,42 @@ package files import ( - "net/netip" "os" "path/filepath" "testing" - "github.com/qdm12/gluetun/internal/configuration/settings" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "gopkg.in/ini.v1" ) -func Test_Source_readWireguard(t *testing.T) { +func ptrTo[T any](value T) *T { return &value } + +func Test_Source_ParseWireguardConf(t *testing.T) { t.Parallel() t.Run("fail reading from file", func(t *testing.T) { t.Parallel() dirPath := t.TempDir() - source := &Source{ - wireguardConfigPath: dirPath, - } - wireguard, err := source.readWireguard() - assert.Equal(t, settings.Wireguard{}, wireguard) + wireguard, err := ParseWireguardConf(dirPath) + assert.Equal(t, WireguardConfig{}, wireguard) assert.Error(t, err) - assert.Regexp(t, `reading file: read .+: is a directory`, err.Error()) + assert.Regexp(t, `loading ini from reader: BOM: read .+: is a directory`, err.Error()) }) t.Run("no file", func(t *testing.T) { t.Parallel() noFile := filepath.Join(t.TempDir(), "doesnotexist") - source := &Source{ - wireguardConfigPath: noFile, - } - wireguard, err := source.readWireguard() - assert.Equal(t, settings.Wireguard{}, wireguard) + wireguard, err := ParseWireguardConf(noFile) + assert.Equal(t, WireguardConfig{}, wireguard) assert.NoError(t, err) }) testCases := map[string]struct { fileContent string - wireguard settings.Wireguard + wireguard WireguardConfig errMessage string }{ "ini load error": { @@ -50,14 +44,14 @@ func Test_Source_readWireguard(t *testing.T) { errMessage: "loading ini from reader: key-value delimiter not found: invalid", }, "empty file": {}, - "interface section parsing error": { + "interface_section_missing": { fileContent: ` -[Interface] -PrivateKey = x +[Peer] +PresharedKey = YJ680VN+dGrdsWNjSFqZ6vvwuiNhbq502ZL3G7Q3o3g= `, - errMessage: "parsing interface section: parsing PrivateKey: " + - "x: wgtypes: failed to parse base64-encoded key: " + - "illegal base64 data at input byte 0", + wireguard: WireguardConfig{ + PreSharedKey: ptrTo("YJ680VN+dGrdsWNjSFqZ6vvwuiNhbq502ZL3G7Q3o3g="), + }, }, "success": { fileContent: ` @@ -69,12 +63,10 @@ DNS = 193.138.218.74 [Peer] PresharedKey = YJ680VN+dGrdsWNjSFqZ6vvwuiNhbq502ZL3G7Q3o3g= `, - wireguard: settings.Wireguard{ + wireguard: WireguardConfig{ PrivateKey: ptrTo("QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8="), PreSharedKey: ptrTo("YJ680VN+dGrdsWNjSFqZ6vvwuiNhbq502ZL3G7Q3o3g="), - Addresses: []netip.Prefix{ - netip.PrefixFrom(netip.AddrFrom4([4]byte{10, 38, 22, 35}), 32), - }, + Addresses: ptrTo("10.38.22.35/32"), }, }, } @@ -88,11 +80,7 @@ PresharedKey = YJ680VN+dGrdsWNjSFqZ6vvwuiNhbq502ZL3G7Q3o3g= err := os.WriteFile(configFile, []byte(testCase.fileContent), 0600) require.NoError(t, err) - source := &Source{ - wireguardConfigPath: configFile, - } - - wireguard, err := source.readWireguard() + wireguard, err := ParseWireguardConf(configFile) assert.Equal(t, testCase.wireguard, wireguard) if testCase.errMessage != "" { @@ -109,34 +97,26 @@ func Test_parseWireguardInterfaceSection(t *testing.T) { testCases := map[string]struct { iniData string - wireguard settings.Wireguard - errMessage string + privateKey *string + addresses *string }{ - "private key error": { - iniData: `[Interface] -PrivateKey = x`, - errMessage: "parsing PrivateKey: x: " + - "wgtypes: failed to parse base64-encoded key: " + - "illegal base64 data at input byte 0", + "no_fields": { + iniData: `[Interface]`, }, - "address error": { + "only_private_key": { iniData: `[Interface] -Address = x +PrivateKey = x `, - errMessage: "parsing address: netip.ParsePrefix(\"x/32\"): ParseAddr(\"x\"): unable to parse IP", + privateKey: ptrTo("x"), }, - "success": { + "all_fields": { iniData: ` [Interface] PrivateKey = QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8= Address = 10.38.22.35/32 `, - wireguard: settings.Wireguard{ - PrivateKey: ptrTo("QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8="), - Addresses: []netip.Prefix{ - netip.PrefixFrom(netip.AddrFrom4([4]byte{10, 38, 22, 35}), 32), - }, - }, + privateKey: ptrTo("QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8="), + addresses: ptrTo("10.38.22.35/32"), }, } @@ -150,93 +130,54 @@ Address = 10.38.22.35/32 iniSection, err := iniFile.GetSection("Interface") require.NoError(t, err) - var wireguard settings.Wireguard - err = parseWireguardInterfaceSection(iniSection, &wireguard) + privateKey, addresses := parseWireguardInterfaceSection(iniSection) - assert.Equal(t, testCase.wireguard, wireguard) - if testCase.errMessage != "" { - assert.EqualError(t, err, testCase.errMessage) - } else { - assert.NoError(t, err) - } + assert.Equal(t, testCase.privateKey, privateKey) + assert.Equal(t, testCase.addresses, addresses) }) } } -func Test_parseINIWireguardKey(t *testing.T) { +func Test_parseWireguardPeerSection(t *testing.T) { t.Parallel() testCases := map[string]struct { - fileContent string - keyName string - key *string - errMessage string + iniData string + preSharedKey *string + publicKey *string + endpointIP *string + endpointPort *string + errMessage string }{ - "key does not exist": { - fileContent: `[Interface]`, - keyName: "PrivateKey", - }, - "bad Wireguard key": { - fileContent: `[Interface] -PrivateKey = x`, - keyName: "PrivateKey", - errMessage: "parsing PrivateKey: x: " + - "wgtypes: failed to parse base64-encoded key: " + - "illegal base64 data at input byte 0", + "public key set": { + iniData: `[Peer] +PublicKey = QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8=`, + publicKey: ptrTo("QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8="), }, - "success": { - fileContent: `[Interface] -PrivateKey = QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8=`, - keyName: "PrivateKey", - key: ptrTo("QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8="), + "endpoint_only_host": { + iniData: `[Peer] +Endpoint = x`, + endpointIP: ptrTo("x"), }, - } - - for testName, testCase := range testCases { - testCase := testCase - t.Run(testName, func(t *testing.T) { - t.Parallel() - - iniFile, err := ini.Load([]byte(testCase.fileContent)) - require.NoError(t, err) - iniSection, err := iniFile.GetSection("Interface") - require.NoError(t, err) - - key, err := parseINIWireguardKey(iniSection, testCase.keyName) - - assert.Equal(t, testCase.key, key) - if testCase.errMessage != "" { - assert.EqualError(t, err, testCase.errMessage) - } else { - assert.NoError(t, err) - } - }) - } -} - -func Test_parseINIWireguardAddress(t *testing.T) { - t.Parallel() - - testCases := map[string]struct { - fileContent string - addresses []netip.Prefix - errMessage string - }{ - "key does not exist": { - fileContent: `[Interface]`, + "endpoint_no_port": { + iniData: `[Peer] +Endpoint = x:`, + endpointIP: ptrTo("x"), + endpointPort: ptrTo(""), }, - "bad address": { - fileContent: `[Interface] -Address = x`, - errMessage: "parsing address: netip.ParsePrefix(\"x/32\"): ParseAddr(\"x\"): unable to parse IP", + "valid_endpoint": { + iniData: `[Peer] +Endpoint = 1.2.3.4:51820`, + endpointIP: ptrTo("1.2.3.4"), + endpointPort: ptrTo("51820"), }, - "success": { - fileContent: `[Interface] -Address = 1.2.3.4/32, 5.6.7.8/32`, - addresses: []netip.Prefix{ - netip.PrefixFrom(netip.AddrFrom4([4]byte{1, 2, 3, 4}), 32), - netip.PrefixFrom(netip.AddrFrom4([4]byte{5, 6, 7, 8}), 32), - }, + "all_set": { + iniData: `[Peer] +PublicKey = QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8= +Endpoint = 1.2.3.4:51820`, + publicKey: ptrTo("QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8="), + endpointIP: ptrTo("1.2.3.4"), + endpointPort: ptrTo("51820"), }, } @@ -245,14 +186,18 @@ Address = 1.2.3.4/32, 5.6.7.8/32`, t.Run(testName, func(t *testing.T) { t.Parallel() - iniFile, err := ini.Load([]byte(testCase.fileContent)) + iniFile, err := ini.Load([]byte(testCase.iniData)) require.NoError(t, err) - iniSection, err := iniFile.GetSection("Interface") + iniSection, err := iniFile.GetSection("Peer") require.NoError(t, err) - addresses, err := parseINIWireguardAddress(iniSection) + preSharedKey, publicKey, endpointIP, + endpointPort := parseWireguardPeerSection(iniSection) - assert.Equal(t, testCase.addresses, addresses) + assert.Equal(t, testCase.preSharedKey, preSharedKey) + assert.Equal(t, testCase.publicKey, publicKey) + assert.Equal(t, testCase.endpointIP, endpointIP) + assert.Equal(t, testCase.endpointPort, endpointPort) if testCase.errMessage != "" { assert.EqualError(t, err, testCase.errMessage) } else { diff --git a/internal/configuration/sources/files/wireguardselection.go b/internal/configuration/sources/files/wireguardselection.go deleted file mode 100644 index f08f3cc29..000000000 --- a/internal/configuration/sources/files/wireguardselection.go +++ /dev/null @@ -1,83 +0,0 @@ -package files - -import ( - "errors" - "fmt" - "net" - "net/netip" - - "github.com/qdm12/gluetun/internal/configuration/settings" - "github.com/qdm12/govalid/port" - "gopkg.in/ini.v1" -) - -var ( - ErrEndpointHostNotIP = errors.New("endpoint host is not an IP") -) - -func (s *Source) readWireguardSelection() (selection settings.WireguardSelection, err error) { - fileStringPtr, err := ReadFromFile(s.wireguardConfigPath) - if err != nil { - return selection, fmt.Errorf("reading file: %w", err) - } - - if fileStringPtr == nil { - return selection, nil - } - - rawData := []byte(*fileStringPtr) - iniFile, err := ini.Load(rawData) - if err != nil { - return selection, fmt.Errorf("loading ini from reader: %w", err) - } - - peerSection, err := iniFile.GetSection("Peer") - if err == nil { - err = parseWireguardPeerSection(peerSection, &selection) - if err != nil { - return selection, fmt.Errorf("parsing peer section: %w", err) - } - } else if !regexINISectionNotExist.MatchString(err.Error()) { - // can never happen - return selection, fmt.Errorf("getting peer section: %w", err) - } - - return selection, nil -} - -func parseWireguardPeerSection(peerSection *ini.Section, - selection *settings.WireguardSelection) (err error) { - publicKeyPtr, err := parseINIWireguardKey(peerSection, "PublicKey") - if err != nil { - return err // error is already wrapped correctly - } else if publicKeyPtr != nil { - selection.PublicKey = *publicKeyPtr - } - - endpointKey, err := peerSection.GetKey("Endpoint") - if err == nil { - endpoint := endpointKey.String() - host, portString, err := net.SplitHostPort(endpoint) - if err != nil { - return fmt.Errorf("splitting endpoint: %w", err) - } - - ip, err := netip.ParseAddr(host) - if err != nil { - return fmt.Errorf("%w: %w", ErrEndpointHostNotIP, err) - } - - endpointPort, err := port.Validate(portString) - if err != nil { - return fmt.Errorf("port from Endpoint key: %w", err) - } - - selection.EndpointIP = ip - selection.EndpointPort = &endpointPort - } else if !regexINIKeyNotExist.MatchString(err.Error()) { - // can never happen - return fmt.Errorf("getting endpoint key: %w", err) - } - - return nil -} diff --git a/internal/configuration/sources/files/wireguardselection_test.go b/internal/configuration/sources/files/wireguardselection_test.go deleted file mode 100644 index 4bedce5cb..000000000 --- a/internal/configuration/sources/files/wireguardselection_test.go +++ /dev/null @@ -1,181 +0,0 @@ -package files - -import ( - "net/netip" - "os" - "path/filepath" - "testing" - - "github.com/qdm12/gluetun/internal/configuration/settings" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "gopkg.in/ini.v1" -) - -func uint16Ptr(n uint16) *uint16 { return &n } - -func Test_Source_readWireguardSelection(t *testing.T) { - t.Parallel() - - t.Run("fail reading from file", func(t *testing.T) { - t.Parallel() - - dirPath := t.TempDir() - source := &Source{ - wireguardConfigPath: dirPath, - } - wireguard, err := source.readWireguardSelection() - assert.Equal(t, settings.WireguardSelection{}, wireguard) - assert.Error(t, err) - assert.Regexp(t, `reading file: read .+: is a directory`, err.Error()) - }) - - t.Run("no file", func(t *testing.T) { - t.Parallel() - - noFile := filepath.Join(t.TempDir(), "doesnotexist") - source := &Source{ - wireguardConfigPath: noFile, - } - wireguard, err := source.readWireguardSelection() - assert.Equal(t, settings.WireguardSelection{}, wireguard) - assert.NoError(t, err) - }) - - testCases := map[string]struct { - fileContent string - selection settings.WireguardSelection - errMessage string - }{ - "ini load error": { - fileContent: "invalid", - errMessage: "loading ini from reader: key-value delimiter not found: invalid", - }, - "empty file": {}, - "peer section parsing error": { - fileContent: ` -[Peer] -PublicKey = x -`, - errMessage: "parsing peer section: parsing PublicKey: " + - "x: wgtypes: failed to parse base64-encoded key: " + - "illegal base64 data at input byte 0", - }, - "success": { - fileContent: ` -[Peer] -PublicKey = QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8= -Endpoint = 1.2.3.4:51820 -`, - selection: settings.WireguardSelection{ - PublicKey: "QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8=", - EndpointIP: netip.AddrFrom4([4]byte{1, 2, 3, 4}), - EndpointPort: uint16Ptr(51820), - }, - }, - } - - for testName, testCase := range testCases { - testCase := testCase - t.Run(testName, func(t *testing.T) { - t.Parallel() - - configFile := filepath.Join(t.TempDir(), "wg.conf") - err := os.WriteFile(configFile, []byte(testCase.fileContent), 0600) - require.NoError(t, err) - - source := &Source{ - wireguardConfigPath: configFile, - } - - wireguard, err := source.readWireguardSelection() - - assert.Equal(t, testCase.selection, wireguard) - if testCase.errMessage != "" { - assert.EqualError(t, err, testCase.errMessage) - } else { - assert.NoError(t, err) - } - }) - } -} - -func Test_parseWireguardPeerSection(t *testing.T) { - t.Parallel() - - testCases := map[string]struct { - iniData string - selection settings.WireguardSelection - errMessage string - }{ - "public key error": { - iniData: `[Peer] -PublicKey = x`, - errMessage: "parsing PublicKey: x: " + - "wgtypes: failed to parse base64-encoded key: " + - "illegal base64 data at input byte 0", - }, - "public key set": { - iniData: `[Peer] -PublicKey = QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8=`, - selection: settings.WireguardSelection{ - PublicKey: "QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8=", - }, - }, - "missing port in endpoint": { - iniData: `[Peer] -Endpoint = x`, - errMessage: "splitting endpoint: address x: missing port in address", - }, - "endpoint host is not IP": { - iniData: `[Peer] -Endpoint = website.com:51820`, - errMessage: "endpoint host is not an IP: ParseAddr(\"website.com\"): unexpected character (at \"website.com\")", - }, - "endpoint port is not valid": { - iniData: `[Peer] -Endpoint = 1.2.3.4:518299`, - errMessage: "port from Endpoint key: port cannot be higher than 65535: 518299", - }, - "valid endpoint": { - iniData: `[Peer] -Endpoint = 1.2.3.4:51820`, - selection: settings.WireguardSelection{ - EndpointIP: netip.AddrFrom4([4]byte{1, 2, 3, 4}), - EndpointPort: uint16Ptr(51820), - }, - }, - "all set": { - iniData: `[Peer] -PublicKey = QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8= -Endpoint = 1.2.3.4:51820`, - selection: settings.WireguardSelection{ - PublicKey: "QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8=", - EndpointIP: netip.AddrFrom4([4]byte{1, 2, 3, 4}), - EndpointPort: uint16Ptr(51820), - }, - }, - } - - for testName, testCase := range testCases { - testCase := testCase - t.Run(testName, func(t *testing.T) { - t.Parallel() - - iniFile, err := ini.Load([]byte(testCase.iniData)) - require.NoError(t, err) - iniSection, err := iniFile.GetSection("Peer") - require.NoError(t, err) - - var selection settings.WireguardSelection - err = parseWireguardPeerSection(iniSection, &selection) - - assert.Equal(t, testCase.selection, selection) - if testCase.errMessage != "" { - assert.EqualError(t, err, testCase.errMessage) - } else { - assert.NoError(t, err) - } - }) - } -} diff --git a/internal/configuration/sources/merge/reader.go b/internal/configuration/sources/merge/reader.go deleted file mode 100644 index b28fc235f..000000000 --- a/internal/configuration/sources/merge/reader.go +++ /dev/null @@ -1,69 +0,0 @@ -package merge - -import ( - "fmt" - "strings" - - "github.com/qdm12/gluetun/internal/configuration/settings" -) - -type ConfigSource interface { - Read() (settings settings.Settings, err error) - ReadHealth() (settings settings.Health, err error) - String() string -} - -type Source struct { - sources []ConfigSource -} - -func New(sources ...ConfigSource) *Source { - return &Source{ - sources: sources, - } -} - -func (s *Source) String() string { - sources := make([]string, len(s.sources)) - for i := range s.sources { - sources[i] = s.sources[i].String() - } - return strings.Join(sources, ", ") -} - -// Read reads the settings for each source, merging unset fields -// with field set by the next source. -// It then set defaults to remaining unset fields. -func (s *Source) Read() (settings settings.Settings, err error) { - for _, source := range s.sources { - settingsFromSource, err := source.Read() - if err != nil { - return settings, fmt.Errorf("reading from %s: %w", source, err) - } - settings.MergeWith(settingsFromSource) - } - settings.SetDefaults() - return settings, nil -} - -// ReadHealth reads the health settings for each source, merging unset fields -// with field set by the next source. -// It then set defaults to remaining unset fields, and validate -// all the fields. -func (s *Source) ReadHealth() (settings settings.Health, err error) { - for _, source := range s.sources { - settingsFromSource, err := source.ReadHealth() - if err != nil { - return settings, fmt.Errorf("reading from %s: %w", source, err) - } - settings.MergeWith(settingsFromSource) - } - settings.SetDefaults() - - err = settings.Validate() - if err != nil { - return settings, err - } - - return settings, nil -} diff --git a/internal/configuration/sources/secrets/health.go b/internal/configuration/sources/secrets/health.go deleted file mode 100644 index ae138270f..000000000 --- a/internal/configuration/sources/secrets/health.go +++ /dev/null @@ -1,5 +0,0 @@ -package secrets - -import "github.com/qdm12/gluetun/internal/configuration/settings" - -func (s *Source) ReadHealth() (settings settings.Health, err error) { return settings, nil } diff --git a/internal/configuration/sources/secrets/helpers.go b/internal/configuration/sources/secrets/helpers.go index a74bed509..02450c82f 100644 --- a/internal/configuration/sources/secrets/helpers.go +++ b/internal/configuration/sources/secrets/helpers.go @@ -1,58 +1,8 @@ package secrets -import ( - "fmt" - "net/netip" - "strings" - - "github.com/qdm12/gluetun/internal/configuration/sources/files" - "github.com/qdm12/gluetun/internal/openvpn/extract" - "github.com/qdm12/gosettings/sources/env" -) - -func (s *Source) readSecretFileAsStringPtr(secretPathEnvKey, defaultSecretPath string) ( - stringPtr *string, err error) { - path := s.env.String(secretPathEnvKey, env.ForceLowercase(false)) - if path == "" { - path = defaultSecretPath - } - return files.ReadFromFile(path) -} - -func (s *Source) readPEMSecretFile(secretPathEnvKey, defaultSecretPath string) ( - base64Ptr *string, err error) { - pemData, err := s.readSecretFileAsStringPtr(secretPathEnvKey, defaultSecretPath) - if err != nil { - return nil, fmt.Errorf("reading secret file: %w", err) - } - - if pemData == nil { - return nil, nil //nolint:nilnil - } - - base64Data, err := extract.PEM([]byte(*pemData)) - if err != nil { - return nil, fmt.Errorf("extracting base64 encoded data from PEM content: %w", err) - } - - return &base64Data, nil -} - -func parseAddresses(addressesCSV string) (addresses []netip.Prefix, err error) { - if addressesCSV == "" { - return nil, nil - } - - addressStrings := strings.Split(addressesCSV, ",") - addresses = make([]netip.Prefix, len(addressStrings)) - for i, addressString := range addressStrings { - addressString = strings.TrimSpace(addressString) - addresses[i], err = netip.ParsePrefix(addressString) - if err != nil { - return nil, fmt.Errorf("parsing address %d of %d: %w", - i+1, len(addressStrings), err) - } +func strPtrToStringIsSet(ptr *string) (s string, isSet bool) { + if ptr == nil { + return "", false } - - return addresses, nil + return *ptr, true } diff --git a/internal/configuration/sources/secrets/helpers_test.go b/internal/configuration/sources/secrets/helpers_test.go deleted file mode 100644 index fe2b6a451..000000000 --- a/internal/configuration/sources/secrets/helpers_test.go +++ /dev/null @@ -1,92 +0,0 @@ -package secrets - -import ( - "os" - "path/filepath" - "testing" - - "github.com/qdm12/gosettings/sources/env" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func ptrTo[T any](value T) *T { return &value } - -func Test_readSecretFileAsStringPtr(t *testing.T) { - t.Parallel() - - testCases := map[string]struct { - source func(tempDir string) Source - secretPathEnvKey string - defaultSecretFileName string - setupFile func(tempDir string) error - stringPtr *string - errWrapped error - errMessage string - }{ - "no_secret_file": { - defaultSecretFileName: "default_secret_file", - secretPathEnvKey: "SECRET_FILE", - }, - "empty_secret_file": { - defaultSecretFileName: "default_secret_file", - secretPathEnvKey: "SECRET_FILE", - setupFile: func(tempDir string) error { - secretFilepath := filepath.Join(tempDir, "default_secret_file") - return os.WriteFile(secretFilepath, nil, os.ModePerm) - }, - stringPtr: ptrTo(""), - }, - "default_secret_file": { - defaultSecretFileName: "default_secret_file", - secretPathEnvKey: "SECRET_FILE", - setupFile: func(tempDir string) error { - secretFilepath := filepath.Join(tempDir, "default_secret_file") - return os.WriteFile(secretFilepath, []byte("A"), os.ModePerm) - }, - stringPtr: ptrTo("A"), - }, - "env_specified_secret_file": { - source: func(tempDir string) Source { - secretFilepath := filepath.Join(tempDir, "secret_file") - environ := []string{"SECRET_FILE=" + secretFilepath} - return Source{env: *env.New(environ, nil)} - }, - defaultSecretFileName: "default_secret_file", - secretPathEnvKey: "SECRET_FILE", - setupFile: func(tempDir string) error { - secretFilepath := filepath.Join(tempDir, "secret_file") - return os.WriteFile(secretFilepath, []byte("B"), os.ModePerm) - }, - stringPtr: ptrTo("B"), - }, - } - - for name, testCase := range testCases { - testCase := testCase - t.Run(name, func(t *testing.T) { - t.Parallel() - tempDir := t.TempDir() - - var source Source - if testCase.source != nil { - source = testCase.source(tempDir) - } - - defaultSecretPath := filepath.Join(tempDir, testCase.defaultSecretFileName) - if testCase.setupFile != nil { - err := testCase.setupFile(tempDir) - require.NoError(t, err) - } - - stringPtr, err := source.readSecretFileAsStringPtr( - testCase.secretPathEnvKey, defaultSecretPath) - - assert.Equal(t, testCase.stringPtr, stringPtr) - assert.ErrorIs(t, err, testCase.errWrapped) - if testCase.errWrapped != nil { - assert.EqualError(t, err, testCase.errMessage) - } - }) - } -} diff --git a/internal/configuration/sources/secrets/httpproxy.go b/internal/configuration/sources/secrets/httpproxy.go deleted file mode 100644 index 653928db8..000000000 --- a/internal/configuration/sources/secrets/httpproxy.go +++ /dev/null @@ -1,27 +0,0 @@ -package secrets - -import ( - "fmt" - - "github.com/qdm12/gluetun/internal/configuration/settings" -) - -func (s *Source) readHTTPProxy() (settings settings.HTTPProxy, err error) { - settings.User, err = s.readSecretFileAsStringPtr( - "HTTPPROXY_USER_SECRETFILE", - "/run/secrets/httpproxy_user", - ) - if err != nil { - return settings, fmt.Errorf("reading HTTP proxy user secret file: %w", err) - } - - settings.Password, err = s.readSecretFileAsStringPtr( - "HTTPPROXY_PASSWORD_SECRETFILE", - "/run/secrets/httpproxy_password", - ) - if err != nil { - return settings, fmt.Errorf("reading HTTP proxy password secret file: %w", err) - } - - return settings, nil -} diff --git a/internal/configuration/sources/secrets/interfaces.go b/internal/configuration/sources/secrets/interfaces.go new file mode 100644 index 000000000..7e1cde18b --- /dev/null +++ b/internal/configuration/sources/secrets/interfaces.go @@ -0,0 +1,5 @@ +package secrets + +type Warner interface { + Warnf(format string, a ...interface{}) +} diff --git a/internal/configuration/sources/secrets/openvpn.go b/internal/configuration/sources/secrets/openvpn.go deleted file mode 100644 index 2d026d085..000000000 --- a/internal/configuration/sources/secrets/openvpn.go +++ /dev/null @@ -1,60 +0,0 @@ -package secrets - -import ( - "fmt" - - "github.com/qdm12/gluetun/internal/configuration/settings" -) - -func (s *Source) readOpenVPN() ( - settings settings.OpenVPN, err error) { - settings.User, err = s.readSecretFileAsStringPtr( - "OPENVPN_USER_SECRETFILE", - "/run/secrets/openvpn_user", - ) - if err != nil { - return settings, fmt.Errorf("reading user file: %w", err) - } - - settings.Password, err = s.readSecretFileAsStringPtr( - "OPENVPN_PASSWORD_SECRETFILE", - "/run/secrets/openvpn_password", - ) - if err != nil { - return settings, fmt.Errorf("reading password file: %w", err) - } - - settings.Key, err = s.readPEMSecretFile( - "OPENVPN_CLIENTKEY_SECRETFILE", - "/run/secrets/openvpn_clientkey", - ) - if err != nil { - return settings, fmt.Errorf("reading client key file: %w", err) - } - - settings.EncryptedKey, err = s.readPEMSecretFile( - "OPENVPN_ENCRYPTED_KEY_SECRETFILE", - "/run/secrets/openvpn_encrypted_key", - ) - if err != nil { - return settings, fmt.Errorf("reading encrypted key file: %w", err) - } - - settings.KeyPassphrase, err = s.readSecretFileAsStringPtr( - "OPENVPN_KEY_PASSPHRASE_SECRETFILE", - "/run/secrets/openvpn_key_passphrase", - ) - if err != nil { - return settings, fmt.Errorf("reading key passphrase file: %w", err) - } - - settings.Cert, err = s.readPEMSecretFile( - "OPENVPN_CLIENTCRT_SECRETFILE", - "/run/secrets/openvpn_clientcrt", - ) - if err != nil { - return settings, fmt.Errorf("reading client certificate file: %w", err) - } - - return settings, nil -} diff --git a/internal/configuration/sources/secrets/reader.go b/internal/configuration/sources/secrets/reader.go index 057744b52..9459aa867 100644 --- a/internal/configuration/sources/secrets/reader.go +++ b/internal/configuration/sources/secrets/reader.go @@ -1,46 +1,104 @@ package secrets import ( - "fmt" "os" + "path/filepath" + "strings" - "github.com/qdm12/gluetun/internal/configuration/settings" - "github.com/qdm12/gosettings/sources/env" + "github.com/qdm12/gluetun/internal/configuration/sources/files" ) type Source struct { - env env.Env + rootDirectory string + environ map[string]string + warner Warner + cached struct { + wireguardLoaded bool + wireguardConf files.WireguardConfig + } } -func New() *Source { - handleDeprecatedKey := (func(deprecatedKey, newKey string))(nil) +func New(warner Warner) (source *Source) { + const rootDirectory = "/run/secrets" + osEnviron := os.Environ() + environ := make(map[string]string, len(osEnviron)) + for _, pair := range osEnviron { + const maxSplit = 2 + split := strings.SplitN(pair, "=", maxSplit) + environ[split[0]] = split[1] + } + return &Source{ - env: *env.New(os.Environ(), handleDeprecatedKey), + rootDirectory: rootDirectory, + environ: environ, + warner: warner, } } func (s *Source) String() string { return "secret files" } -func (s *Source) Read() (settings settings.Settings, err error) { - settings.VPN, err = s.readVPN() - if err != nil { - return settings, err +func (s *Source) Get(key string) (value string, isSet bool) { + if key == "" { + return "", false } - - settings.HTTPProxy, err = s.readHTTPProxy() - if err != nil { - return settings, err + // TODO v4 custom environment variable to set the secrets parent directory + // and not to set each secret file to a specific path + envKey := strings.ToUpper(key) + envKey = strings.ReplaceAll(envKey, "-", "_") + envKey += "_SECRETFILE" // TODO v4 change _SECRETFILE to _FILE + path := s.environ[envKey] + if path == "" { + path = filepath.Join(s.rootDirectory, key) } - settings.Shadowsocks, err = s.readShadowsocks() - if err != nil { - return settings, err + // Special file parsing + switch key { + // TODO timezone from /etc/localtime + case "openvpn_clientcrt", "openvpn_clientkey": + value, isSet, err := files.ReadPEMFile(path) + if err != nil { + s.warner.Warnf("skipping %s: parsing PEM: %s", path, err) + } + return value, isSet + case "wireguard_private_key": + privateKey := s.lazyLoadWireguardConf().PrivateKey + if privateKey != nil { + return *privateKey, true + } // else continue to read from individual secret file + case "wireguard_preshared_key": + preSharedKey := s.lazyLoadWireguardConf().PreSharedKey + if preSharedKey != nil { + return *preSharedKey, true + } // else continue to read from individual secret file + case "wireguard_addresses": + addresses := s.lazyLoadWireguardConf().Addresses + if addresses != nil { + return *addresses, true + } // else continue to read from individual secret file + case "wireguard_public_key": + return strPtrToStringIsSet(s.lazyLoadWireguardConf().PublicKey) + case "vpn_endpoint_ip": + return strPtrToStringIsSet(s.lazyLoadWireguardConf().EndpointIP) + case "vpn_endpoint_port": + return strPtrToStringIsSet(s.lazyLoadWireguardConf().EndpointPort) } - settings.VPN.Wireguard, err = s.readWireguard() + value, isSet, err := files.ReadFromFile(path) if err != nil { - return settings, fmt.Errorf("reading Wireguard: %w", err) + s.warner.Warnf("skipping %s: reading file: %s", path, err) } + return value, isSet +} - return settings, nil +func (s *Source) KeyTransform(key string) string { + switch key { + // TODO v4 remove these irregular cases + case "OPENVPN_KEY": + return "openvpn_clientkey" + case "OPENVPN_CERT": + return "openvpn_clientcrt" + default: + key = strings.ToLower(key) // HTTPROXY_USER -> httpproxy_user + return key + } } diff --git a/internal/configuration/sources/secrets/reader_test.go b/internal/configuration/sources/secrets/reader_test.go new file mode 100644 index 000000000..4e7a4508e --- /dev/null +++ b/internal/configuration/sources/secrets/reader_test.go @@ -0,0 +1,102 @@ +package secrets + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_Source_Get(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + makeSource func(tempDir string) (source *Source, err error) + key string + value string + isSet bool + }{ + "empty_key": { + makeSource: func(tempDir string) (source *Source, err error) { + return &Source{ + rootDirectory: tempDir, + environ: map[string]string{}, + }, nil + }, + }, + "no_secret_file": { + makeSource: func(tempDir string) (source *Source, err error) { + return &Source{ + rootDirectory: tempDir, + environ: map[string]string{}, + }, nil + }, + key: "test_file", + }, + "empty_secret_file": { + makeSource: func(tempDir string) (source *Source, err error) { + secretFilepath := filepath.Join(tempDir, "test_file") + err = os.WriteFile(secretFilepath, nil, os.ModePerm) + if err != nil { + return nil, err + } + return &Source{ + rootDirectory: tempDir, + environ: map[string]string{}, + }, nil + }, + key: "test_file", + isSet: true, + }, + "default_secret_file": { + makeSource: func(tempDir string) (source *Source, err error) { + secretFilepath := filepath.Join(tempDir, "test_file") + err = os.WriteFile(secretFilepath, []byte{'A'}, os.ModePerm) + if err != nil { + return nil, err + } + return &Source{ + rootDirectory: tempDir, + environ: map[string]string{}, + }, nil + }, + key: "test_file", + value: "A", + isSet: true, + }, + "env_specified_secret_file": { + makeSource: func(tempDir string) (source *Source, err error) { + secretFilepath := filepath.Join(tempDir, "test_file_custom") + err = os.WriteFile(secretFilepath, []byte{'A'}, os.ModePerm) + if err != nil { + return nil, err + } + return &Source{ + rootDirectory: tempDir, + environ: map[string]string{ + "TEST_FILE_SECRETFILE": secretFilepath, + }, + }, nil + }, + key: "test_file", + value: "A", + isSet: true, + }, + } + + for name, testCase := range testCases { + testCase := testCase + t.Run(name, func(t *testing.T) { + t.Parallel() + + source, err := testCase.makeSource(t.TempDir()) + require.NoError(t, err) + + value, isSet := source.Get(testCase.key) + assert.Equal(t, testCase.value, value) + assert.Equal(t, testCase.isSet, isSet) + }) + } +} diff --git a/internal/configuration/sources/secrets/shadowsocks.go b/internal/configuration/sources/secrets/shadowsocks.go deleted file mode 100644 index aa8380e60..000000000 --- a/internal/configuration/sources/secrets/shadowsocks.go +++ /dev/null @@ -1,19 +0,0 @@ -package secrets - -import ( - "fmt" - - "github.com/qdm12/gluetun/internal/configuration/settings" -) - -func (s *Source) readShadowsocks() (settings settings.Shadowsocks, err error) { - settings.Password, err = s.readSecretFileAsStringPtr( - "SHADOWSOCKS_PASSWORD_SECRETFILE", - "/run/secrets/shadowsocks_password", - ) - if err != nil { - return settings, fmt.Errorf("reading Shadowsocks password secret file: %w", err) - } - - return settings, nil -} diff --git a/internal/configuration/sources/secrets/vpn.go b/internal/configuration/sources/secrets/vpn.go deleted file mode 100644 index 2e3579d6a..000000000 --- a/internal/configuration/sources/secrets/vpn.go +++ /dev/null @@ -1,21 +0,0 @@ -package secrets - -import ( - "fmt" - - "github.com/qdm12/gluetun/internal/configuration/settings" -) - -func (s *Source) readVPN() (vpn settings.VPN, err error) { - vpn.OpenVPN, err = s.readOpenVPN() - if err != nil { - return vpn, fmt.Errorf("reading OpenVPN settings: %w", err) - } - - vpn.Wireguard, err = s.readWireguard() - if err != nil { - return vpn, fmt.Errorf("reading Wireguard settings: %w", err) - } - - return vpn, nil -} diff --git a/internal/configuration/sources/secrets/wireguard.go b/internal/configuration/sources/secrets/wireguard.go index a1ff7c8ca..1af3c0a40 100644 --- a/internal/configuration/sources/secrets/wireguard.go +++ b/internal/configuration/sources/secrets/wireguard.go @@ -1,52 +1,27 @@ package secrets import ( - "fmt" + "os" + "path/filepath" - "github.com/qdm12/gluetun/internal/configuration/settings" "github.com/qdm12/gluetun/internal/configuration/sources/files" ) -func (s *Source) readWireguard() (settings settings.Wireguard, err error) { - wireguardConf, err := s.readSecretFileAsStringPtr( - "WIREGUARD_CONF_SECRETFILE", - "/run/secrets/wg0.conf", - ) - if err != nil { - return settings, fmt.Errorf("reading Wireguard conf secret file: %w", err) - } else if wireguardConf != nil { - // Wireguard ini config file takes precedence over individual secrets - return files.ParseWireguardConf([]byte(*wireguardConf)) - } - - settings.PrivateKey, err = s.readSecretFileAsStringPtr( - "WIREGUARD_PRIVATE_KEY_SECRETFILE", - "/run/secrets/wireguard_private_key", - ) - if err != nil { - return settings, fmt.Errorf("reading private key file: %w", err) +func (s *Source) lazyLoadWireguardConf() files.WireguardConfig { + if s.cached.wireguardLoaded { + return s.cached.wireguardConf } - settings.PreSharedKey, err = s.readSecretFileAsStringPtr( - "WIREGUARD_PRESHARED_KEY_SECRETFILE", - "/run/secrets/wireguard_preshared_key", - ) - if err != nil { - return settings, fmt.Errorf("reading preshared key file: %w", err) + path := os.Getenv("WIREGUARD_CONF_SECRETFILE") + if path == "" { + path = filepath.Join(s.rootDirectory, "wg0.conf") } - wireguardAddressesCSV, err := s.readSecretFileAsStringPtr( - "WIREGUARD_ADDRESSES_SECRETFILE", - "/run/secrets/wireguard_addresses", - ) + s.cached.wireguardLoaded = true + var err error + s.cached.wireguardConf, err = files.ParseWireguardConf(path) if err != nil { - return settings, fmt.Errorf("reading addresses file: %w", err) - } else if wireguardAddressesCSV != nil { - settings.Addresses, err = parseAddresses(*wireguardAddressesCSV) - if err != nil { - return settings, fmt.Errorf("parsing addresses: %w", err) - } + s.warner.Warnf("skipping Wireguard config: %s", err) } - - return settings, nil + return s.cached.wireguardConf } diff --git a/internal/httpserver/settings.go b/internal/httpserver/settings.go index 90f542f14..9b67c533d 100644 --- a/internal/httpserver/settings.go +++ b/internal/httpserver/settings.go @@ -8,8 +8,8 @@ import ( "time" "github.com/qdm12/gosettings" + "github.com/qdm12/gosettings/validate" "github.com/qdm12/gotree" - "github.com/qdm12/govalid/address" ) type Settings struct { @@ -34,12 +34,12 @@ type Settings struct { } func (s *Settings) SetDefaults() { - s.Address = gosettings.DefaultString(s.Address, ":8000") + s.Address = gosettings.DefaultComparable(s.Address, ":8000") const defaultReadTimeout = 3 * time.Second - s.ReadHeaderTimeout = gosettings.DefaultNumber(s.ReadHeaderTimeout, defaultReadTimeout) - s.ReadTimeout = gosettings.DefaultNumber(s.ReadTimeout, defaultReadTimeout) + s.ReadHeaderTimeout = gosettings.DefaultComparable(s.ReadHeaderTimeout, defaultReadTimeout) + s.ReadTimeout = gosettings.DefaultComparable(s.ReadTimeout, defaultReadTimeout) const defaultShutdownTimeout = 3 * time.Second - s.ShutdownTimeout = gosettings.DefaultNumber(s.ShutdownTimeout, defaultShutdownTimeout) + s.ShutdownTimeout = gosettings.DefaultComparable(s.ShutdownTimeout, defaultShutdownTimeout) } func (s Settings) Copy() Settings { @@ -53,26 +53,15 @@ func (s Settings) Copy() Settings { } } -func (s *Settings) MergeWith(other Settings) { - s.Address = gosettings.MergeWithString(s.Address, other.Address) - s.Handler = gosettings.MergeWithInterface(s.Handler, other.Handler) - if s.Logger == nil { - s.Logger = other.Logger - } - s.ReadHeaderTimeout = gosettings.MergeWithNumber(s.ReadHeaderTimeout, other.ReadHeaderTimeout) - s.ReadTimeout = gosettings.MergeWithNumber(s.ReadTimeout, other.ReadTimeout) - s.ShutdownTimeout = gosettings.MergeWithNumber(s.ShutdownTimeout, other.ShutdownTimeout) -} - func (s *Settings) OverrideWith(other Settings) { - s.Address = gosettings.OverrideWithString(s.Address, other.Address) - s.Handler = gosettings.OverrideWithInterface(s.Handler, other.Handler) + s.Address = gosettings.OverrideWithComparable(s.Address, other.Address) + s.Handler = gosettings.OverrideWithComparable(s.Handler, other.Handler) if other.Logger != nil { s.Logger = other.Logger } - s.ReadHeaderTimeout = gosettings.OverrideWithNumber(s.ReadHeaderTimeout, other.ReadHeaderTimeout) - s.ReadTimeout = gosettings.OverrideWithNumber(s.ReadTimeout, other.ReadTimeout) - s.ShutdownTimeout = gosettings.OverrideWithNumber(s.ShutdownTimeout, other.ShutdownTimeout) + s.ReadHeaderTimeout = gosettings.OverrideWithComparable(s.ReadHeaderTimeout, other.ReadHeaderTimeout) + s.ReadTimeout = gosettings.OverrideWithComparable(s.ReadTimeout, other.ReadTimeout) + s.ShutdownTimeout = gosettings.OverrideWithComparable(s.ShutdownTimeout, other.ShutdownTimeout) } var ( @@ -84,8 +73,7 @@ var ( ) func (s Settings) Validate() (err error) { - uid := os.Getuid() - err = address.Validate(s.Address, address.OptionListening(uid)) + err = validate.ListeningAddress(s.Address, os.Getuid()) if err != nil { return err } diff --git a/internal/httpserver/settings_test.go b/internal/httpserver/settings_test.go index 331ca91db..27a8edbda 100644 --- a/internal/httpserver/settings_test.go +++ b/internal/httpserver/settings_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - "github.com/qdm12/govalid/address" + "github.com/qdm12/gosettings/validate" "github.com/stretchr/testify/assert" ) @@ -98,68 +98,6 @@ func Test_Settings_Copy(t *testing.T) { } } -func Test_Settings_MergeWith(t *testing.T) { - t.Parallel() - - someHandler := http.NewServeMux() - someLogger := &testLogger{} - - testCases := map[string]struct { - settings Settings - other Settings - expected Settings - }{ - "merge empty with empty": {}, - "merge empty with filled": { - other: Settings{ - Address: ":8001", - Handler: someHandler, - Logger: someLogger, - ReadHeaderTimeout: time.Second, - ReadTimeout: time.Second, - ShutdownTimeout: time.Second, - }, - expected: Settings{ - Address: ":8001", - Handler: someHandler, - Logger: someLogger, - ReadHeaderTimeout: time.Second, - ReadTimeout: time.Second, - ShutdownTimeout: time.Second, - }, - }, - "merge filled with empty": { - settings: Settings{ - Address: ":8001", - Handler: someHandler, - Logger: someLogger, - ReadHeaderTimeout: time.Second, - ReadTimeout: time.Second, - ShutdownTimeout: time.Second, - }, - expected: Settings{ - Address: ":8001", - Handler: someHandler, - Logger: someLogger, - ReadHeaderTimeout: time.Second, - ReadTimeout: time.Second, - ShutdownTimeout: time.Second, - }, - }, - } - - for name, testCase := range testCases { - testCase := testCase - t.Run(name, func(t *testing.T) { - t.Parallel() - - testCase.settings.MergeWith(testCase.other) - - assert.Equal(t, testCase.expected, testCase.settings) - }) - } -} - func Test_Settings_OverrideWith(t *testing.T) { t.Parallel() @@ -257,12 +195,12 @@ func Test_Settings_Validate(t *testing.T) { errWrapped error errMessage string }{ - "bad address": { + "bad_address": { settings: Settings{ - Address: "noport", + Address: "address:notanint", }, - errWrapped: address.ErrValueNotValid, - errMessage: "value is not valid: address noport: missing port in address", + errWrapped: validate.ErrPortNotAnInteger, + errMessage: "port value is not an integer: notanint", }, "nil handler": { settings: Settings{ @@ -332,7 +270,7 @@ func Test_Settings_Validate(t *testing.T) { err := testCase.settings.Validate() assert.ErrorIs(t, err, testCase.errWrapped) - if err != nil { + if testCase.errMessage != "" { assert.EqualError(t, err, testCase.errMessage) } }) diff --git a/internal/portforward/service/settings.go b/internal/portforward/service/settings.go index 4948195e8..8deee3019 100644 --- a/internal/portforward/service/settings.go +++ b/internal/portforward/service/settings.go @@ -29,11 +29,11 @@ func (s Settings) Copy() (copied Settings) { func (s *Settings) OverrideWith(update Settings) { s.Enabled = gosettings.OverrideWithPointer(s.Enabled, update.Enabled) - s.PortForwarder = gosettings.OverrideWithInterface(s.PortForwarder, update.PortForwarder) - s.Filepath = gosettings.OverrideWithString(s.Filepath, update.Filepath) - s.Interface = gosettings.OverrideWithString(s.Interface, update.Interface) - s.ServerName = gosettings.OverrideWithString(s.ServerName, update.ServerName) - s.ListeningPort = gosettings.OverrideWithNumber(s.ListeningPort, update.ListeningPort) + s.PortForwarder = gosettings.OverrideWithComparable(s.PortForwarder, update.PortForwarder) + s.Filepath = gosettings.OverrideWithComparable(s.Filepath, update.Filepath) + s.Interface = gosettings.OverrideWithComparable(s.Interface, update.Interface) + s.ServerName = gosettings.OverrideWithComparable(s.ServerName, update.ServerName) + s.ListeningPort = gosettings.OverrideWithComparable(s.ListeningPort, update.ListeningPort) } var ( diff --git a/internal/pprof/settings.go b/internal/pprof/settings.go index b8bca3fcf..8f7cef370 100644 --- a/internal/pprof/settings.go +++ b/internal/pprof/settings.go @@ -7,6 +7,7 @@ import ( "github.com/qdm12/gluetun/internal/httpserver" "github.com/qdm12/gosettings" + "github.com/qdm12/gosettings/reader" "github.com/qdm12/gotree" ) @@ -28,9 +29,9 @@ type Settings struct { func (s *Settings) SetDefaults() { s.Enabled = gosettings.DefaultPointer(s.Enabled, false) - s.HTTPServer.Address = gosettings.DefaultString(s.HTTPServer.Address, "localhost:6060") + s.HTTPServer.Address = gosettings.DefaultComparable(s.HTTPServer.Address, "localhost:6060") const defaultReadTimeout = 5 * time.Minute // for CPU profiling - s.HTTPServer.ReadTimeout = gosettings.DefaultNumber(s.HTTPServer.ReadTimeout, defaultReadTimeout) + s.HTTPServer.ReadTimeout = gosettings.DefaultComparable(s.HTTPServer.ReadTimeout, defaultReadTimeout) s.HTTPServer.SetDefaults() } @@ -43,13 +44,6 @@ func (s Settings) Copy() (copied Settings) { } } -func (s *Settings) MergeWith(other Settings) { - s.Enabled = gosettings.MergeWithPointer(s.Enabled, other.Enabled) - s.BlockProfileRate = gosettings.MergeWithPointer(s.BlockProfileRate, other.BlockProfileRate) - s.MutexProfileRate = gosettings.MergeWithPointer(s.MutexProfileRate, other.MutexProfileRate) - s.HTTPServer.MergeWith(other.HTTPServer) -} - func (s *Settings) OverrideWith(other Settings) { s.Enabled = gosettings.OverrideWithPointer(s.Enabled, other.Enabled) s.BlockProfileRate = gosettings.OverrideWithPointer(s.BlockProfileRate, other.BlockProfileRate) @@ -97,3 +91,24 @@ func (s Settings) ToLinesNode() (node *gotree.Node) { func (s Settings) String() string { return s.ToLinesNode().String() } + +func (s *Settings) Read(r *reader.Reader) (err error) { + s.Enabled, err = r.BoolPtr("PPROF_ENABLED") + if err != nil { + return err + } + + s.BlockProfileRate, err = r.IntPtr("PPROF_BLOCK_PROFILE_RATE") + if err != nil { + return err + } + + s.MutexProfileRate, err = r.IntPtr("PPROF_MUTEX_PROFILE_RATE") + if err != nil { + return err + } + + s.HTTPServer.Address = r.String("PPROF_HTTP_SERVER_ADDRESS") + + return nil +} diff --git a/internal/pprof/settings_test.go b/internal/pprof/settings_test.go index 9ede0dcce..5331ce7dc 100644 --- a/internal/pprof/settings_test.go +++ b/internal/pprof/settings_test.go @@ -6,7 +6,7 @@ import ( "time" "github.com/qdm12/gluetun/internal/httpserver" - "github.com/qdm12/govalid/address" + "github.com/qdm12/gosettings/validate" "github.com/stretchr/testify/assert" ) @@ -108,65 +108,6 @@ func Test_Settings_Copy(t *testing.T) { } } -func Test_Settings_MergeWith(t *testing.T) { - t.Parallel() - - testCases := map[string]struct { - settings Settings - other Settings - expected Settings - }{ - "merge empty with empty": {}, - "merge empty with filled": { - other: Settings{ - Enabled: boolPtr(true), - BlockProfileRate: intPtr(1), - MutexProfileRate: intPtr(1), - HTTPServer: httpserver.Settings{ - Address: ":8001", - }, - }, - expected: Settings{ - Enabled: boolPtr(true), - BlockProfileRate: intPtr(1), - MutexProfileRate: intPtr(1), - HTTPServer: httpserver.Settings{ - Address: ":8001", - }, - }, - }, - "merge filled with empty": { - settings: Settings{ - Enabled: boolPtr(true), - BlockProfileRate: intPtr(1), - MutexProfileRate: intPtr(1), - HTTPServer: httpserver.Settings{ - Address: ":8001", - }, - }, - expected: Settings{ - Enabled: boolPtr(true), - BlockProfileRate: intPtr(1), - MutexProfileRate: intPtr(1), - HTTPServer: httpserver.Settings{ - Address: ":8001", - }, - }, - }, - } - - for name, testCase := range testCases { - testCase := testCase - t.Run(name, func(t *testing.T) { - t.Parallel() - - testCase.settings.MergeWith(testCase.other) - - assert.Equal(t, testCase.expected, testCase.settings) - }) - } -} - func Test_Settings_OverrideWith(t *testing.T) { t.Parallel() @@ -280,10 +221,12 @@ func Test_Settings_Validate(t *testing.T) { settings: Settings{ BlockProfileRate: intPtr(0), MutexProfileRate: intPtr(0), - HTTPServer: httpserver.Settings{}, + HTTPServer: httpserver.Settings{ + Address: ":x", + }, }, - errWrapped: address.ErrValueNotValid, - errMessage: "value is not valid: missing port in address", + errWrapped: validate.ErrPortNotAnInteger, + errMessage: "port value is not an integer: x", }, "valid settings": { settings: Settings{ @@ -309,7 +252,7 @@ func Test_Settings_Validate(t *testing.T) { err := testCase.settings.Validate() assert.ErrorIs(t, err, testCase.errWrapped) - if err != nil { + if testCase.errMessage != "" { assert.EqualError(t, err, testCase.errMessage) } })