Compare commits

...

259 Commits

Author SHA1 Message Date
Quentin McGaw
8f04a05b45 fix(vpnsecure): allow empty OpenVPN user+password 2022-09-11 20:18:42 +00:00
Quentin McGaw
aa53436e56 chore(lint): upgrade golangci-lint to v1.49.0
- Add linter `interfacebloat` and fix code issues
- Add linter `reassign`
- Remove deprecated linter `nosnakecase`
2022-08-24 21:48:24 +00:00
Quentin McGaw
8dfaebc737 chore(all): remove deprecated io/ioutil import 2022-08-24 21:43:37 +00:00
Quentin McGaw
062b6a276c fix(settings): read PEM files but b64 env vars
- Extract base64 data from PEM files and secret files
- Environment variables are not PEM encoded and only the base64 data
- Affects OpenVPN certificate, key and encrypted key
2022-08-24 17:48:45 +00:00
Quentin McGaw
647cd07de7 feat(surfshark): update servers data 2022-08-24 13:04:34 +00:00
Quentin McGaw
a530c84c5f fix(surshark): remove invalid retro-servers 2022-08-24 13:04:18 +00:00
Quentin McGaw
0bb320065e feat(server): patch VPN settings
- `PUT` at `/v1/vpn/settings`
- Undocumented, experimental for now
2022-08-21 23:36:48 +00:00
Quentin McGaw
d685d78e74 feat(server): add vpn route to replace /openvpn 2022-08-21 23:29:25 +00:00
Quentin McGaw
48896176e5 chore(server): do not redact openvpn credentials from response 2022-08-21 22:04:04 +00:00
Quentin McGaw
54dcf28b31 chore(server): replace 404 with 401 for unsupported routes and methods 2022-08-21 22:02:06 +00:00
Quentin McGaw
f8bf32bb34 docs(readme): add slickvpn to list of providers 2022-08-16 00:02:34 +00:00
Quentin McGaw
748923021c fix(ci): permissions for labels workflow 2022-08-15 23:58:33 +00:00
Quentin McGaw
a182e3503b feat: add VPNsecure.me support (#848)
- `OPENVPN_ENCRYPTED_KEY` environment variable 
- `OPENVPN_ENCRYPTED_KEY_SECRETFILE` environment variable 
- `OPENVPN_KEY_PASSPHRASE` environment variable 
- `OPENVPN_KEY_PASSPHRASE_SECRETFILE` environment variable 
- `PREMIUM_ONLY` environment variable
- OpenVPN user and password not required for vpnsecure provider
2022-08-15 16:54:58 -07:00
Quentin McGaw
991cfb8659 chore(ci): limit labels workflow to not forked 2022-08-15 23:53:29 +00:00
Richard Hodgson
d0dfc21e2b feat: SlickVPN Support (#961)
- `internal/updater/html` package
- Add unit tests for slickvpn updating code
- Change shared html package to be more share-able
- Split html utilities in multiple files
- Fix processing .ovpn files with prefix space

Authored by @Rohaq 
Co-authored-by: Quentin McGaw <quentin.mcgaw@gmail.com>
2022-08-15 08:25:06 -07:00
dependabot[bot]
617bd0c600 Chore(deps): Bump github.com/stretchr/testify from 1.7.5 to 1.8.0 (#1052) 2022-08-13 12:16:57 -07:00
dependabot[bot]
349b5429ba Chore(deps): Bump docker/build-push-action from 3.1.0 to 3.1.1 (#1098) 2022-08-13 12:16:44 -07:00
Quentin McGaw
8db2944749 chore(settings): OpenVPN ClientCrt -> Cert 2022-08-13 18:59:07 +00:00
Quentin McGaw
5986432a22 chore(settings): OpenVPN ClientKey -> Key 2022-08-13 18:58:09 +00:00
Quentin McGaw
652daec509 Change: OPENVPN_CLIENTKEY -> OPENVPN_KEY
- No breaking change since this was undocumented
2022-08-13 18:56:37 +00:00
Quentin McGaw
f94d4b761a Change: OPENVPN_CLIENTCRT -> OPENVPN_CERT
- No breaking change since this was undocumented
2022-08-13 18:55:29 +00:00
Quentin McGaw
1ab74e6bb3 chore: OpenVPN user and password as nullable
- Username and password can be the empty string for custom provider
2022-08-13 18:01:26 +00:00
dependabot[bot]
8e101d49a1 Chore(deps): Bump github.com/breml/rootcerts from 0.2.4 to 0.2.6 (#1058) 2022-08-12 17:01:45 -07:00
Quentin McGaw
7c08e8f607 chore(lint): add asasalint and usestdlibvars 2022-08-12 23:54:20 +00:00
Quentin McGaw
a4caa61c47 chore(lint): add nosnakecase linter 2022-08-12 23:53:47 +00:00
Quentin McGaw
ebae167815 chore(lint): golangci-lint v1.47.2 -> v1.48.0
- Remove deprecated `ifshort` linter
- Fix bad `//nolint:gomnd` comment
2022-08-12 23:52:30 +00:00
Quentin McGaw
a6f00f2fb2 chore(lint): upgrade golangci-lint to v1.47.2
- Fix Slowloris attacks on HTTP servers
- Force set default of 5 minutes for pprof read timeout
- Change `ShutdownTimeout` to time.Duration since it cannot be set to 0
2022-08-01 21:09:16 +00:00
dependabot[bot]
877617cc53 Chore(deps): Bump docker/build-push-action from 3.0.0 to 3.1.0 (#1073) 2022-07-23 20:08:34 -07:00
Quentin McGaw
2800588ef7 feat(expressvpn): update servers data 2022-07-18 18:01:08 +00:00
Quentin McGaw
f5efa42aaf chore(lint): remove some linters
- remove duplicate `predeclared`
- remove commented `varnamelen` and `wrapcheck`
2022-07-17 23:11:42 +00:00
Hey
10bd0e1505 fix(readme): typo sercice to service (#1067) 2022-07-15 19:52:04 -04:00
Quentin McGaw
a4c80b3045 chore(ci): add mocks check
- Check for missing `//go:generate` comments
- Check for outdated mocks
2022-07-04 00:39:01 +00:00
Quentin McGaw
dbb71bd695 chore(mocks): use common mocks for ivpn and ipvanish 2022-07-04 00:34:48 +00:00
dependabot[bot]
a544f6e604 Chore(deps): Bump github.com/breml/rootcerts from 0.2.3 to 0.2.4 (#1033) 2022-07-03 16:50:38 -07:00
dependabot[bot]
a18e026b70 Chore(deps): Bump github.com/stretchr/testify from 1.7.2 to 1.7.5 (#1042) 2022-07-03 16:50:27 -07:00
Quentin McGaw
0413a0a1ab chore(ci): rework docker hub description workflow
- Run only on base repository
- Rename job from `dockerHubDescription` to `docker-hub-description`
- Limit permissions of job to read only
- Remove unneeded names for steps
2022-07-03 14:31:49 +00:00
Quentin McGaw
cb6e9cb761 docs(readme): add links to add a provider 2022-07-03 13:39:47 +00:00
Quentin McGaw
420ae40901 feat(dev): Add provider example package 2022-07-02 21:04:57 +00:00
Quentin McGaw
34e67f9f99 chore(markdown): alphabetically sorted headers 2022-07-02 20:58:43 +00:00
Quentin McGaw
18c53aa597 docs(readme): simplify heading description 2022-07-02 20:58:43 +00:00
Quentin McGaw
6d2f9b9508 chore(updater): check servers have minimal information 2022-07-02 20:58:43 +00:00
Quentin McGaw
6826b05d58 chore(all): remove all package comments 2022-07-02 20:58:43 +00:00
barino28
9f959dbc6a fix(expressvpn): OpenVPN fragment option and add ciphers (#1047)
* Fragment was defined in `OpenVPNProviderSettings` but was not written to the OpenVPN configuration file.
* Added two additional ciphers to the configuration for ExpressVPN

Authored-by: barino86 <barino@mac.com>
2022-06-29 05:23:16 -07:00
Quentin McGaw
87dbae5745 hotfix(fastestvpn): re-fix Openvpn configuration
- add `auth sha256` option
- remove `remote-cert-tls server` option
2022-06-26 21:29:05 +00:00
Quentin McGaw
037f19e852 hotfix(publicip): revert back JSON to public_ip 2022-06-26 18:08:11 +00:00
Quentin McGaw
62ad8bcd8f fix(pia): set port forward file owned with PUID and PGID 2022-06-25 15:44:29 +00:00
Quentin McGaw
2805c3388a hotfix(fastestvpn): add remote-cert-tls server 2022-06-25 15:16:38 +00:00
Quentin McGaw
535297dcf5 chore: extract.PEM replaces PEM parse functions 2022-06-24 23:10:00 +00:00
Quentin McGaw
b3b6933ef4 chore(lint): review exclude rules 2022-06-20 13:36:24 +00:00
Quentin McGaw
edbbcc041a fix(protonvpn): set free field for free servers 2022-06-18 18:30:27 +00:00
Quentin McGaw
d430ebc34f feat(protonvpn): update servers data 2022-06-18 18:30:05 +00:00
Quentin McGaw
0e9abc6e1d chore(tests): modify JSON tests to not need all providers listed 2022-06-18 15:08:59 +00:00
Quentin McGaw
0c0dd10766 chore(dev): add VSCode launch.json
- Credits to @Rohaq
2022-06-18 00:17:09 +00:00
Quentin McGaw
75454be6b6 fix(pprof): override operation in global settings 2022-06-18 00:16:14 +00:00
Quentin McGaw
4952e3b74e docs(bug): fix render of logs to be plain text 2022-06-18 00:15:29 +00:00
Quentin McGaw
04b34a266c chore(deps): update go4.org/unsafe/assume-no-moving-gc
- Allow development on Go 1.18 without `ASSUME_NO_MOVING_GC_UNSAFE_RISK_IT_WITH=go1.18`
2022-06-17 23:59:39 +00:00
Quentin McGaw
89b6a031b0 chore(vpn): add check for empty public key for Wireguard 2022-06-12 15:59:48 +00:00
Quentin McGaw
d4c6a9bdb5 feat(storage): log time difference as a friendly duration 2022-06-12 15:35:04 +00:00
Quentin McGaw
cdc29d48b7 chore(ci): build PR images as pr-N (#1026)
- Remove event validity check (unneeded and was buggy)
- Remove `branch` metadata trigger since it conflicts with the PR trigger
2022-06-12 08:33:16 -07:00
dependabot[bot]
f4b464a7cf Chore(deps): Bump github.com/stretchr/testify from 1.7.1 to 1.7.2 (#1016) 2022-06-12 08:31:53 -07:00
dependabot[bot]
76690d3add Chore(deps): Bump crazy-max/ghaction-github-labeler from 3 to 4 (#1007) 2022-06-12 07:07:25 -07:00
Quentin McGaw
9898387579 feat(updater): Configurable min ratio
- `UPDATER_MIN_RATIO` variable
- `-minratio` flag for CLI operation
2022-06-12 14:03:00 +00:00
Quentin McGaw
1ea15a1a13 chore(protonvpn): document to remove SERVER_NAMES 2022-06-12 01:58:46 +00:00
Quentin McGaw
bda6707685 chore(all): remove unexported interfaces 2022-06-12 01:15:14 +00:00
Quentin McGaw
89277828ac chore(publicip): internal/publicip/ipinfo package 2022-06-12 01:11:22 +00:00
Quentin McGaw
83b4a3fe55 chore(publicip): refactoring
- Exported `Fetcher` interface
- Inject `Fetcher` to publicip loop and updaters
- Get public IP and information at the same time
- Only query ipinfo.io
- Make `MultiInfo` part of the `Fetch` object
2022-06-12 00:46:08 +00:00
Quentin McGaw
45c9e780c0 chore(updater): rename presolver to parallelResolver 2022-06-11 20:12:35 +00:00
Quentin McGaw
33b8f5f596 chore(settings): updater DNS address as string 2022-06-11 20:11:20 +00:00
Quentin McGaw
447a7c9891 updater: refactoring and set DNS server correctly
- Fix CLI operation not setting DNS server
- Fix periodic operation not setting DNS server
- Set DNS address for resolution once at start for both CLI and periodic operation
- Inject resolver to each provider instead of creating it within
- Use resolver settings on every call to `.Resolve` method, instead of passing it to constructor
- Move out minServers check from resolver
2022-06-11 19:47:46 +00:00
Quentin McGaw
1bd355ab96 chore(lint): add ireturn linter 2022-06-11 01:34:45 +00:00
Quentin McGaw
578ef768ab chore(all): return concrete types, accept interfaces
- Remove exported interfaces unused locally
- Define interfaces to accept arguments
- Return concrete types, not interfaces
2022-06-11 01:34:30 +00:00
Quentin McGaw
0378fe4a7b chore(all): Providers containing all provider objects
- Share the same providers for updater and vpn
- Initialise all providers at start
- Get from `Providers` instead of constructing on every run
2022-06-10 00:47:56 +00:00
Quentin McGaw
ebd94723c1 chore(updater): incorporate FetchServers method in Provider interface
- Each provider interface can now fetch updated servers data
- Rename each provider updater subpackage name to `updater`
- Updater constructor does not take a settings struct
- Updater update method takes in a slice of provider strings
2022-06-09 23:47:41 +00:00
Quentin McGaw
11b55abff3 fix(protonvpn): remove duplicate entry IPs 2022-06-09 22:11:40 +00:00
Quentin McGaw
7f32b43895 fix(pia): load custom PIA certificate for API 2022-06-09 17:11:24 +00:00
Quentin McGaw
899f10c35e chore(resolver): export structs instead of interfaces 2022-06-09 17:11:24 +00:00
Quentin McGaw
415cb7a945 chore(updater): create resolver in provider updater
- Pass min servers to resolve call
- Set settings when constructing resolver
- Construct resolver in each provider updater
- No more common resolver for all providers
2022-06-09 17:11:24 +00:00
Quentin McGaw
e37f557cd5 chore(provider): add Name() method per provider 2022-06-09 17:11:24 +00:00
Quentin McGaw
79f213d97a chore(updater): rename GetServers to FetchServers 2022-06-09 17:11:24 +00:00
Quentin McGaw
11e1c9f9bb feat(protonvpn): update hardcoded servers data 2022-06-09 16:41:04 +00:00
Quentin McGaw
3ff3816d77 fix(pia): restrict custom port choice 2022-06-09 16:36:59 +00:00
Quentin McGaw
c0bdae8baf fix(protonvpn): restrict custom port choice 2022-06-09 16:36:17 +00:00
Quentin McGaw
46e6bd16c9 fix(pia): remove duplicate log of pf data expiration 2022-06-09 16:34:28 +00:00
Quentin McGaw
5359257c65 hotfix(pia): port forwarding to use server name 2022-06-06 18:09:21 +00:00
Quentin McGaw
5e659dc5b3 feat(storage): add keep field for servers 2022-06-06 03:04:58 +00:00
Quentin McGaw
85e9d7d522 docs(maintenance): update document 2022-06-06 02:58:58 +00:00
Quentin McGaw
b71c8e58f4 fix(vpn): do not close wait error channel on consumer side 2022-06-06 02:56:40 +00:00
Quentin McGaw
e998372ce2 feat(ipvanish): update servers data and remove duplicates 2022-06-06 02:24:58 +00:00
Quentin McGaw
1216326867 chore(storage): common sorting for all servers 2022-06-06 02:24:54 +00:00
Quentin McGaw
f53f0cfffd feat(surfshark): update servers data 2022-06-06 01:41:00 +00:00
Quentin McGaw
f5f65d534a fix(ci): publish job trigger fixed 2022-06-05 16:01:40 +00:00
Quentin McGaw
684cef6eab hotfix(openvpn): openvpn udp specific lines added 2022-06-05 15:48:14 +00:00
Quentin McGaw
b4f6ae030d hotfix(purevpn): add missing key-direction 1 2022-06-05 15:44:33 +00:00
Quentin McGaw
e95c94294f feat(pia): update servers data 2022-06-05 15:20:03 +00:00
Quentin McGaw
36b504609b chore(all): memory and thread safe storage
- settings: get filter choices from storage for settings validation
- updater: update servers to the storage
- storage: minimal deep copying and data duplication
- storage: add merged servers mutex for thread safety
- connection: filter servers in storage
- formatter: format servers to Markdown in storage
- PIA: get server by name from storage directly
- Updater: get servers count from storage directly
- Updater: equality check done in storage, fix #882
2022-06-05 15:19:16 +00:00
Quentin McGaw
1e6b4ed5eb chore(provider): rename test functions to Test_Provider_GetConnection 2022-06-05 14:59:47 +00:00
Quentin McGaw
0549326dfb chore(updater): tiny code changes
- Remove unneeded ctx error check in cyberghost updating code
- Move global scope caser to function local scope
- Return error if updating a single provider in `UpdateServers`
- Add comments on different error paths in `UpdateServers`
2022-06-04 13:50:29 +00:00
Quentin McGaw
87c6ebe1c5 feat(purevpn): update servers data 2022-05-31 14:17:33 +00:00
Quentin McGaw
f0afac243b feat(privatevpn): update servers data 2022-05-31 14:16:41 +00:00
dependabot[bot]
53472077f4 Chore(deps): Bump docker/setup-buildx-action from 1 to 2 (#977) 2022-05-29 11:31:09 -07:00
dependabot[bot]
55afdf33e1 Chore(deps): Bump docker/setup-qemu-action from 1 to 2 (#978) 2022-05-29 11:28:05 -07:00
dependabot[bot]
d3c1f9263c Chore(deps): Bump docker/build-push-action from 2.10.0 to 3.0.0 (#979) 2022-05-29 11:27:55 -07:00
dependabot[bot]
6341d1dda6 Chore(deps): Bump docker/metadata-action from 3 to 4 (#980) 2022-05-29 11:27:44 -07:00
dependabot[bot]
e62e1883c2 Chore(deps): Bump docker/login-action from 1 to 2 (#981) 2022-05-29 11:27:33 -07:00
Quentin McGaw
501b98dbd3 chore(ci): skip workflow for required verify job 2022-05-29 17:33:35 +00:00
Derzsi Dániel
029fd1da1f feat(docker): upgrade Alpine from 3.15 to 3.16 (#1005) 2022-05-29 10:30:10 -07:00
Quentin McGaw
fd0267efef chore(ci): merge codeql job in CI workflow 2022-05-29 17:23:55 +00:00
Quentin McGaw
4414366370 chore(ci): restrict permissions to read actions+contents 2022-05-29 17:23:55 +00:00
Quentin McGaw
08553bc90b chore(ci): only publish image for qdm12/gluetun 2022-05-29 17:23:54 +00:00
Quentin McGaw
6f850c4ad4 chore(ci): merge dependabot and fork workflows in ci workflow 2022-05-29 17:23:48 +00:00
Quentin McGaw
8e1316bd8a chore(storage): minor refactoring
- Unexport `SyncServers`
- Re-generate mock file
- Remove single use function
2022-05-28 22:51:19 +00:00
Quentin McGaw
b345368257 hotfix(storage): JSON provider versioning safety 2022-05-28 22:44:14 +00:00
Quentin McGaw
90dd3b1b5c chore(storage): only pass hardcoded versions to read file 2022-05-28 22:36:16 +00:00
Quentin McGaw
22455ac76f chore(updater): shared not enough servers error 2022-05-28 22:02:18 +00:00
Quentin McGaw
eb18eaf0a9 fix(wireguard): continue on ipv6 route add permission denial 2022-05-28 21:06:21 +00:00
Quentin McGaw
90c6c8485b chore(updater): common GetServers signature
- Log warnings when running outside of CLI mode
- Remove updater CLI bool setting
- Warnings are logged in updating functions
2022-05-28 20:58:50 +00:00
Quentin McGaw
381089ebdf chore(storage): rename InfoErrorer to Infoer (bad name) 2022-05-28 16:05:19 +00:00
Quentin McGaw
292813831d chore(updater): internal/updater/loop subpackage
- Do not export updater interface
- Export updater struct
- Define local interfaces where needed
- More restrictive updater loop interface in http control server
- Inject `Updater` into updater loop as an interface
2022-05-28 16:03:59 +00:00
Quentin McGaw
991d75a1d0 chore(provider): rename all BuildConf to OpenVPNConfig 2022-05-27 22:04:14 +00:00
Quentin McGaw
d9dfb81cb4 feat(perfect privacy): update servers data 2022-05-27 21:56:52 +00:00
Quentin McGaw
67a9cacb61 hotfix(custom): allow empty servers data 2022-05-27 21:47:41 +00:00
Quentin McGaw
a91eb95456 chore(internal/provider): rename all structs to Provider 2022-05-27 18:05:04 +00:00
Quentin McGaw
a295269518 hotfix(formatter): cyberghost not forced as format 2022-05-27 17:50:14 +00:00
Quentin McGaw
42904b6749 chore(all): move sub-packages to internal/provider 2022-05-27 17:48:51 +00:00
Quentin McGaw
364f9de756 feat(env): clean env variable values
- Remove surrounding spaces
- Remove suffix new line characters
2022-05-27 17:27:54 +00:00
Quentin McGaw
7fd45cf17f feat(wireguard): add debug logs for IPv6 detection
- To debug issue #998
- Enable with `LOG_LEVEL=debug`
2022-05-27 17:27:53 +00:00
Quentin McGaw
eb71cfb144 chore(deps): upgrade gopkg.in/yaml.v3 to v3.0.1
- fix 'vulnerability' alert on github
- no impact really since it's just used in unit tests
- checked with `go mod why gopkg.in/yaml.v3`
2022-05-27 17:27:53 +00:00
Quentin McGaw
48e469917e chore(ci): remove tidy check
- Not really needed with newer `go install`
- Conflicts with Go 1.17 go.mod format
- Conflicts with manual indirect dependency upgrade
2022-05-27 17:27:53 +00:00
Quentin McGaw
4bcd8ee9f5 chore(constants): add internal/constants/openvpn package 2022-05-27 16:29:49 +00:00
Quentin McGaw
1b2bcf901a chore(surfshark): add package internal/provider/surshark/server
- Merge `internal/models/location.go` and `internal/constants/surfshark.go` into `internal/provider/surfshark/servers/locationdata.go`
2022-05-27 16:29:48 +00:00
Quentin McGaw
306de8feda chore(constants): add internal/provider/privateinternetacess/presets package 2022-05-27 16:29:48 +00:00
Quentin McGaw
e3696f1eea chore(constants): inline Openvpn values in each provider 2022-05-27 16:29:47 +00:00
Quentin McGaw
7ff14a356c chore(internal/providers): simplify OpenVPN config building 2022-05-27 16:29:47 +00:00
Quentin McGaw
4bde50fb3a chore(all): use casers instead of strings.Title
- Add `golang.org/x/text` dependency
- Update code to use `cases.Title(language.English)`
2022-05-27 16:29:41 +00:00
Quentin McGaw
bd0868d764 chore(all): provider to servers map in allServers
- Simplify formatting CLI
- Simplify updater code
- Simplify filter choices for config validation
- Simplify all servers deep copying
- Custom JSON marshaling methods for `AllServers`
- Simplify provider constructor switch
- Simplify storage merging
- Simplify storage reading and extraction
- Simplify updating code
2022-05-27 16:17:53 +00:00
Quentin McGaw
5ffe8555ba chore(lint): upgrade golangci-lint from v1.44.2 to v1.46.2
- Add linter `execinquery`
- Add linter `nosprintfhostport`
2022-05-27 00:52:25 +00:00
Quentin McGaw
78ccbb21cd change(servers.json): change provider names
- From `pia` to `private internet access`
- From `perfectprivacy` to `perfect privacy`
- From `vpnunlimited` to `vpn unlimited`
- This is done to match string constants in the code for another refactor
- Reset each of these providers servers version to `1`.
2022-05-27 00:47:58 +00:00
Quentin McGaw
92dbe1ebad chore(cli): refactor FormatServers to use provider strings 2022-05-08 19:05:36 +00:00
Quentin McGaw
2eec60cdd2 chore(custom): validate Openvpn file earlier 2022-05-07 19:33:21 +00:00
Quentin McGaw
da8c104ebd chore(internal/provider/utils): unexport functions 2022-05-07 19:33:12 +00:00
Quentin McGaw
0ef7b66047 chore(internal/provider): GetConnection test 2022-05-07 19:33:05 +00:00
Quentin McGaw
e32d251cc1 hotfix(windscribe): OpenVPN certificate validation 2022-05-07 07:05:24 +00:00
Quentin McGaw
9dd5e7bf1d fix: PUID and PGID as 32 bit unsigned integers 2022-05-01 16:29:56 +00:00
Quentin McGaw
b6de6035f6 hotfix(nordvpn): use aes-256-cbc before GCM 2022-04-28 13:47:24 +00:00
Quentin McGaw
88ccaf0b83 feat(torguard): update servers data 2022-04-26 11:01:42 +00:00
Quentin McGaw
52c8bc075f feat(nordvpn): update servers data 2022-04-26 11:01:05 +00:00
Quentin McGaw
2537cd5271 fix(port-forwarding): loop exit from vpn loop 2022-04-25 08:31:32 +00:00
Quentin McGaw
db91625de4 fix(pia): port forwarding certificate
- Do not use custom PIA certificate
- Only use OS certificates
- Update unit test
2022-04-25 08:31:27 +00:00
Quentin McGaw
df78386fbe chore(ci): add codeql analysis 2022-04-23 12:30:15 -04:00
Quentin McGaw
a1d70f740a fix(nordvpn): allow aes-256-gcm for Openvpn 2.4 2022-04-23 12:53:24 +00:00
Quentin McGaw
187f42277a fix(pia): hide escaped url query values 2022-04-23 11:21:08 +00:00
Quentin McGaw
e1f89bb569 fix(health): HEALTH_VPN_DURATION_ADDITION 2022-04-23 11:09:24 +00:00
Quentin McGaw
1d94f8ab2b chore(storage): remove unneeded VPN default 2022-04-23 11:09:04 +00:00
Quentin McGaw
045ecabb78 chore(updater): set vpn field for all providers
- Bump servers model versions for all providers except mullvad, ivpn, windscribe
- Do not leave `vpn` JSON field empty for any server
2022-04-23 11:08:59 +00:00
Quentin McGaw
e6c3cb078a chore(storage): tcp and udp fields for all servers
- Updater code sets UDP and TCP compatibility for all providers
- Increase servers.json model versions for affected providers (mullvad, windscribe, privado, protonvpn, privatevpn)
- Remove retro-compatibility server defaults
- Update all affected providers servers data (mullvad, windscribe, privado, protonvpn, privatevpn)
2022-04-23 10:23:41 +00:00
Quentin McGaw
afa51b3ff6 hotfix(storage): servers json versions updated 2022-04-22 21:12:27 +00:00
Quentin McGaw
f9c80b2285 hotfix(privatevpn): add missing IP addresses 2022-04-22 21:03:38 +00:00
Quentin McGaw
fc5cf44b2c fix(firewall): iptables detection improved
1. Try setting a dummy output rule
2. Remove the dummy output rule
3. Get the INPUT table policy
4. Set the INPUT table policy to its existing policy
2022-04-22 17:23:57 +00:00
Quentin McGaw
0c0f1663b1 chore: simplify provider GetConnection 2022-04-20 15:16:55 +00:00
Quentin McGaw
306d8494d6 hotfix(servers): assume UDP+TCP if not precised 2022-04-19 11:52:05 +00:00
Quentin McGaw
f5c00c3e2d chore(filter): common filter for all providers 2022-04-18 17:08:31 +00:00
Quentin McGaw
ac9571c6b2 chore(storage): runtime defaults on servers data
- `openvpn` default VPN protocol for servers
- True UDP if VPN protocol is Wireguard
2022-04-18 12:08:26 +00:00
Quentin McGaw
934fafb64b chore(constants): internal/constants/vpn package 2022-04-18 11:14:07 +00:00
Quentin McGaw
d51514015f chore(storage): simplify reading of server file 2022-04-18 11:14:02 +00:00
Quentin McGaw
a9cfd16d53 chore(validation): uniformize server filters build 2022-04-18 07:27:00 +00:00
Quentin McGaw
1a6f26fa3b feat(nordvpn): remove OpenVPN compression 2022-04-18 07:26:53 +00:00
Quentin McGaw
0dd723b29f chore(provider): add safety connection count check 2022-04-17 16:23:53 +00:00
Quentin McGaw
7ad6fc8e73 docs(maintenance): update document 2022-04-17 16:21:21 +00:00
Quentin McGaw
31c7e6362b chore(devcontainer): multiple changes and fixes
- Fix windows script sourcing
- Remove image name to avoid conflicts
- Bind mount normally without `:z`
- Install `htop`
2022-04-17 16:21:21 +00:00
Quentin McGaw
072b42d867 chore(v4): add v4 comments about server names 2022-04-17 16:21:21 +00:00
Quentin McGaw
5d66c193aa chore(models): common Server & Servers for all providers (#943) 2022-04-17 16:21:19 +00:00
Quentin McGaw
aa729515b9 chore(models): streamline all server models IPs (#942)
- Use `IPs []net.IP` for all server models
- Use `ips` JSON field for all server models
- Merge IPv4 and IPv6 addresses together for Mullvad
2022-04-17 16:18:34 +00:00
Quentin McGaw
54b7e23974 chore(constants): internal/constants/providers
- New package to avoid package import cycles
2022-04-16 19:30:26 +00:00
Quentin McGaw
ad80e0c1ab feat(protonvpn): update servers data 2022-04-16 17:52:53 +00:00
Quentin McGaw
5d7b278957 change(protonvpn): change server name JSON field from name to server_name 2022-04-16 17:51:15 +00:00
dependabot[bot]
678caaf6a0 Chore(deps): Bump docker/build-push-action from 2.9.0 to 2.10.0 (#893) 2022-04-15 12:23:38 -04:00
dependabot[bot]
7228cd7b12 Chore(deps): Bump github.com/breml/rootcerts from 0.2.2 to 0.2.3 (#926) 2022-04-15 12:22:55 -04:00
Martin Bjeldbak Madsen
7b598a3534 docs(readme): remove announcement (#938) 2022-04-15 12:22:30 -04:00
Quentin McGaw
9cdc9e9153 feat(pia): server data updated 2022-04-11 21:29:16 +00:00
Quentin McGaw
71ab0416b0 fix(iptables): use OUTPUT chain for test instead of INPUT 2022-04-11 21:05:12 +00:00
Quentin McGaw
10a13bc8a7 fix(health): change default target address to cloudflare.com:443 2022-04-11 20:21:15 +00:00
Mirco Ianese
be386a8e33 feat(fastestvpn): update servers data (#923) 2022-04-02 13:31:00 -04:00
Quentin McGaw
c33fb8bb97 fix(env): OPENVPN_FLAGS functionality 2022-03-31 20:49:01 +00:00
Quentin McGaw
20f20f051b fix(firewall): iptables support detection
- Add dummy rule to `INPUT` to test for iptables support
- This may resolve #896
2022-03-30 09:03:25 +00:00
Quentin McGaw
179274ade0 feat(log): use github.com/qdm12/log library 2022-03-30 09:03:20 +00:00
Quentin McGaw
84607e332b chore(server): use httpserver package for control server 2022-03-30 09:00:42 +00:00
Quentin McGaw
8186ef2342 chore(httpserver): remove name field 2022-03-30 09:00:36 +00:00
Mirco Ianese
19b184adba fix(purevpn): update servers Zip file download URL (#915)
- Fix PureVPN zip file download link
- Update all PureVPN server information
2022-03-28 15:47:40 -04:00
Quentin McGaw
a97fd35d6e fix(ci): openvpn 2.4.12-r0 2022-03-28 17:32:56 +00:00
dependabot[bot]
470ca020e2 Chore(deps): Bump github.com/stretchr/testify from 1.7.0 to 1.7.1 (#897)
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.7.0 to 1.7.1.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.7.0...v1.7.1)

---
updated-dependencies:
- dependency-name: github.com/stretchr/testify
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-03-28 13:23:08 -04:00
dependabot[bot]
f64d7c4343 Chore(deps): Bump peter-evans/dockerhub-description from 2 to 3 (#908)
Bumps [peter-evans/dockerhub-description](https://github.com/peter-evans/dockerhub-description) from 2 to 3.
- [Release notes](https://github.com/peter-evans/dockerhub-description/releases)
- [Commits](https://github.com/peter-evans/dockerhub-description/compare/v2...v3)

---
updated-dependencies:
- dependency-name: peter-evans/dockerhub-description
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-03-28 13:22:58 -04:00
Quentin McGaw
c6f68a64e6 fix(health): use TCP dialing instead of ping
- `HEALTH_TARGET_ADDRESS` to replace `HEALTH_ADDRESS_TO_PING`
- Remove `github.com/go-ping/ping` dependency
- Dial TCP the target address, appending `:443` if port is not set
2022-03-22 08:50:56 +00:00
Quentin McGaw
5aaa122460 feat(protonvpn): update server information 2022-03-17 19:25:33 +00:00
Quentin McGaw
de169c027f feat(privatevpn): update server information 2022-03-16 10:21:49 +00:00
Quentin McGaw
314c9663a2 fix(privatevpn): update servers without hostname 2022-03-16 10:21:42 +00:00
Quentin McGaw
21995eb3e3 feat(privado): update server information 2022-03-16 10:06:10 +00:00
Quentin McGaw
6fc700bd62 feat(mullvad): update server information 2022-03-16 10:05:01 +00:00
Quentin McGaw
acdbe2163e chore(protonvpn): remove unused exit IPs field 2022-03-16 09:44:57 +00:00
Quentin McGaw
c3a231e0ab chore(storage): omit empty fields in servers.json 2022-03-16 09:43:47 +00:00
Quentin McGaw
984e143336 feat(shutdown): log out OS signal name 2022-03-15 08:16:08 +00:00
Quentin McGaw
e2ba2f82c0 feat(routing): add IPv6 inbound routing 2022-03-13 19:36:45 +00:00
Quentin McGaw
ace5e97e68 fix(routing): only set routes for IPv4 default routes 2022-03-13 14:40:17 +00:00
Quentin McGaw
82d42297e8 chore(routing): remove unused LocalSubnetGetter 2022-03-13 13:32:19 +00:00
Quentin McGaw
f99d5e8656 feat(firewall): use all default routes
- Accept output traffic from all default routes through VPN interface
- Accept output from all default routes to outbound subnets
- Accept all input traffic on ports for all default routes
- Add IP rules for all default routes
2022-03-13 13:26:33 +00:00
dependabot[bot]
0795008c23 Chore(deps): Bump docker/build-push-action from 2.8.0 to 2.9.0 (#832)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 2.8.0 to 2.9.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v2.8.0...v2.9.0)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-03-09 16:09:51 -05:00
dependabot[bot]
c975a86a70 Chore(deps): Bump actions/checkout from 2.4.0 to 3 (#870)
Bumps [actions/checkout](https://github.com/actions/checkout) from 2.4.0 to 3.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v2.4.0...v3)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-03-09 16:09:28 -05:00
Quentin McGaw
69eee345d2 feat(ivpn): allow no password for account IDs
- When matching `i-xxxx-xxxx-xxxx` username
- When matching `ivpn-xxxx-xxxx-xxxx` username
2022-03-09 21:01:25 +00:00
Quentin McGaw
48afc05bcb docs(readme): re-add /dev/net/tun since some OS need it 2022-03-09 11:20:05 +00:00
Quentin McGaw
39a62f5db7 feat(firewall): improve error message when NET_ADMIN is missing 2022-03-09 11:16:10 +00:00
Quentin McGaw
006b218ade feat(firewall): auto-detect which iptables
- On `iptables` error, try to use `iptables-nft`
- On `ip6tables` error, try to use `ip6tables-nft`
2022-02-26 22:55:22 +00:00
Quentin McGaw
2b09b9c290 fix(ci): docker metadata image tags
- Move metata as top step in publish workflow
- Simplify `v0.x.x` check
- Dynamically determine base branch
2022-02-26 16:15:31 +00:00
Quentin McGaw
c42865b3d9 chore(ci): merge misspell workflow in ci workflow 2022-02-26 14:01:15 +00:00
Quentin McGaw
836f021a87 chore(lint): add containedctx, decorder and errchkjson 2022-02-26 13:49:53 +00:00
Quentin McGaw
26b049b361 fix(ci): docker/metadata-action logic 2022-02-26 13:39:35 +00:00
Quentin McGaw
e75627365d chore(lint): upgrade golangci-lint to v1.44.2 2022-02-20 21:26:38 +00:00
Quentin McGaw
ae0334c930 chore(sources): wrap error with source string 2022-02-20 03:04:16 +00:00
Quentin McGaw
920ad8b54b chore(errors): review all errors in codebase 2022-02-20 02:58:16 +00:00
Quentin McGaw
ac4a4f83fc chore(settings): split openvpn validation in functions 2022-02-20 00:08:55 +00:00
Quentin McGaw
a4652c2d32 feat(validation): reject server filters ignored for some VPN providers 2022-02-18 14:06:13 +00:00
Quentin McGaw
c40d4e075e chore(validation): move functions from constants
- Move validation functions from `internal/constants` to `internal/configuration/settings/validation`
- Concatenate all OpenVPN constants in `internal/constants/openvpn.go`
2022-02-13 01:21:25 +00:00
Quentin McGaw
95967136d3 feat(firewall): faster setup 75ms to 10ms 2022-02-09 13:41:38 +00:00
Quentin McGaw (desktop)
576c1ee0c5 fix(env): accept uppercase SHADOWSOCKS_CIPHER 2022-02-09 12:33:47 +00:00
Quentin McGaw (desktop)
5d4032edf4 fix(env): accept uppercase OPENVPN_PROTOCOL 2022-02-09 12:33:24 +00:00
Quentin McGaw (desktop)
ff3f84f9fd hotfix(env): OPENVPN_CIPHERS empty parsing 2022-02-06 22:58:23 +00:00
Quentin McGaw
2a19b68b9a hotfix(env): fix parsing of unset server filters 2022-02-06 20:13:40 +00:00
Quentin McGaw
ed6c010aff hotfix(env): fix BLOCK_SURVEILLANCE parsing 2022-02-06 20:06:58 +00:00
Quentin McGaw
783fb38e41 hotfix(env): allow empty VPN_ENDPOINT_IP 2022-02-06 20:02:45 +00:00
Quentin McGaw
fcab4ae3c6 chore(env): SERVER_NAMES variable
- With retro-compatibility with `SERVER_NAME`
2022-02-06 19:59:07 +00:00
Quentin McGaw
a69c456965 chore(env): SERVER_HOSTNAMES variable
- With retro-compatibility with `SERVER_HOSTNAME`
2022-02-06 19:59:07 +00:00
Quentin McGaw
0e6db2f1c5 chore(env): SERVER_REGIONS variable
- With retro-compatibility with `REGION`
2022-02-06 19:59:07 +00:00
Quentin McGaw
7aab18d197 chore(env): SERVER_CITIES variable
- With retro-compatibility with `CITY`
2022-02-06 19:59:07 +00:00
Quentin McGaw
d6b39e66d1 chore(env): SERVER_COUNTRIES variable
- With retro-compatibility with `COUNTRY`
2022-02-06 19:59:07 +00:00
Quentin McGaw
3f5c72d898 chore(env): simplify Cyberghost retro logic 2022-02-06 19:59:07 +00:00
Quentin McGaw
691ade794b chore(env): PRIVATE_INTERNET_ACCESS_VPN_PORT_FORWARDING_STATUS_FILE
- With retro-compatibility with `PORT_FORWARDING_STATUS_FILE`
2022-02-06 19:59:07 +00:00
Quentin McGaw
1693c4ed8a chore(env): PRIVATE_INTERNET_ACCESS_VPN_PORT_FORWARDING
- With retro-compatibility with `PORT_FORWARDING`
2022-02-06 19:59:07 +00:00
Quentin McGaw
ae9b3279c3 chore(env): PRIVATE_INTERNET_ACCESS_OPENVPN_ENCRYPTION_PRESET variable
- With retro-compatibility with `PIA_ENCRYPTION` and `ENCRYPTION`
2022-02-06 19:59:07 +00:00
Quentin McGaw
04956e45c7 chore(env): OPENVPN_CIPHERS variable
- With retro-compatibility with `OPENVPN_CIPHER`
2022-02-06 19:59:07 +00:00
Quentin McGaw
027664af7b chore(env): VPN_SERVICE_PROVIDER variable
- With retro-compatibility with `VPNSP`
2022-02-06 19:59:07 +00:00
Quentin McGaw
f8d5f76bdf chore(env): WIREGUARD_ADDRESSES variable
- With retro-compatibility with `WIREGUARD_ADDRESS`
2022-02-06 19:59:07 +00:00
Quentin McGaw
114f9be47f chore(env): DNS_ADDRESS variable
- With retro-compatibility with `DNS_PLAINTEXT_ADDRESS`
2022-02-06 19:59:07 +00:00
Quentin McGaw
c73369e11c chore(constants): remove and move constant paths
- Remove unused paths
- Move paths to inline constants if used only once
2022-02-06 19:59:07 +00:00
Quentin McGaw
5603e25542 chore(env): VPN_INTERFACE
- With retro-compatibility with `OPENVPN_INTERFACE`
- With retro-compatibility with `WIREGUARD_INTERFACE`
2022-02-06 19:59:07 +00:00
Quentin McGaw
0d8cb66d43 chore(env): getEnvWithRetro helper function 2022-02-06 19:59:07 +00:00
Quentin McGaw
e7e4cfca4c fix(env): Retro-compatible precedence order for variables with defaults set in Dockerfile
- `BLOCK_NSA` has precedence over `BLOCK_SURVEILLANCE`
- `HEALTH_OPENVPN_DURATION_ADDITION` has precedence over `HEALTH_VPN_DURATION_ADDITION`
- `HEALTH_OPENVPN_DURATION_INITIAL` has precendence over `HEALTH_VPN_DURATION_INITIAL`
- Chain of precedence: `PROXY` > `TINYPROXY` > `HTTPPROXY`
- Chain of precedence: `PROXY_LOG_LEVEL` > `TINYPROXY_LOG` > `HTTPPROXY_LOG`
- `PROTOCOL` has precendence over `OPENVPN_PROTOCOL`
- `IP_STATUS_FILE` has precendence over `PUBLICIP_FILE`
- `SHADOWSOCKS_PORT` has precedence over `SHADOWSOCKS_LISTENING_ADDRESS`
- `SHADOWSOCKS_METHOD` has precedence over `SHADOWSOCKS_CIPHER`
2022-02-06 19:59:07 +00:00
Quentin McGaw
fd23f1a29b chore(env): do not validate control server port 2022-02-06 19:59:07 +00:00
Quentin McGaw
57481e3dd7 fix(cyberghost): compat log if COUNTRY is empty 2022-02-06 19:59:07 +00:00
Quentin McGaw
53952b143f fix(server): allow to bind on a random port 2022-02-06 19:59:07 +00:00
Quentin McGaw
e7b0f4c6be feat(vpn): VPN_ENDPOINT_PORT
- Deprecate `OPENVPN_PORT`
- Deprecate `WIREGUARD_ENDPOINT_PORT`
2022-02-06 19:59:07 +00:00
Quentin McGaw
ea143c0c9a feat(vpn): VPN_ENDPOINT_PORT
- Deprecate `OPENVPN_PORT`
- Deprecate `WIREGUARD_ENDPOINT_PORT`
2022-01-28 00:10:23 +00:00
Quentin McGaw
a951110461 feat(vpn): VPN_ENDPOINT_IP
- Deprecate `OPENVPN_TARGET_IP`
- Deprecate `WIREGUARD_ENDPOINT_IP`
2022-01-28 00:09:58 +00:00
Quentin McGaw
7a8f5f53d5 feat(openvpn): OPENVPN_PROCESS_USER and deprecates OPENVPN_ROOT 2022-01-27 23:34:19 +00:00
Quentin McGaw
1b585159d1 feat(server): HTTP_CONTROL_SERVER_PORT to HTTP_CONTROL_SERVER_ADDRESS 2022-01-27 23:15:08 +00:00
Quentin McGaw (desktop)
f3692cd47f feat(mullvad): OWNED to OWNED_ONLY 2022-01-27 14:12:25 +00:00
Quentin McGaw (desktop)
15800fd4ff feat(expressvpn): update built-in data 2022-01-27 13:01:08 +00:00
Quentin McGaw (desktop)
9fb085f361 hotfix(updater): do not default to custom 2022-01-27 12:57:27 +00:00
Quentin McGaw
1e3f878470 feat(updater): UPDATER_VPN_SERVICE_PROVIDERS
- Updater defaults to update the VPN provider in use if enabled
2022-01-26 22:41:06 +00:00
dependabot[bot]
bcf9bfa5d3 Chore(deps): Bump docker/build-push-action from 2.7.0 to 2.8.0 (#801)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 2.7.0 to 2.8.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v2.7.0...v2.8.0)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-01-26 17:35:50 -05:00
dependabot[bot]
56bdc1f0ae Chore(deps): Bump github.com/breml/rootcerts from 0.2.1 to 0.2.2 (#812)
Bumps [github.com/breml/rootcerts](https://github.com/breml/rootcerts) from 0.2.1 to 0.2.2.
- [Release notes](https://github.com/breml/rootcerts/releases)
- [Commits](https://github.com/breml/rootcerts/compare/v0.2.1...v0.2.2)

---
updated-dependencies:
- dependency-name: github.com/breml/rootcerts
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-01-26 17:35:36 -05:00
Quentin McGaw
9de6428585 feat(pprof): add pprof HTTP server (#807)
- `PPROF_ENABLED=no`
- `PPROF_BLOCK_PROFILE_RATE=0`
- `PPROF_MUTEX_PROFILE_RATE=0`
- `PPROF_HTTP_SERVER_ADDRESS=":6060"`
2022-01-26 17:23:55 -05:00
654 changed files with 106547 additions and 79194 deletions

View File

@@ -1,2 +1,2 @@
FROM qmcgaw/godevcontainer
RUN apk add wireguard-tools
RUN apk add wireguard-tools htop

View File

@@ -8,7 +8,7 @@
"vscode"
],
"shutdownAction": "stopCompose",
"postCreateCommand": "~/.windows.sh && go mod download && go mod tidy",
"postCreateCommand": "source ~/.windows.sh && go mod download && go mod tidy",
"workspaceFolder": "/workspace",
"extensions": [
"golang.go",
@@ -25,6 +25,7 @@
"bajdzis.vscode-database", // Supports connections to mysql or postgres, over SSL, socked
"IBM.output-colorizer", // Colorize your output/test logs
"mohsen1.prettify-json", // Prettify JSON data
"github.copilot",
],
"settings": {
"files.eol": "\n",

View File

@@ -3,7 +3,6 @@ version: "3.7"
services:
vscode:
build: .
image: godevcontainer
devices:
- /dev/net/tun:/dev/net/tun
volumes:
@@ -11,16 +10,16 @@ services:
# Docker socket to access Docker server
- /var/run/docker.sock:/var/run/docker.sock
# Docker configuration
- ~/.docker:/root/.docker:z
- ~/.docker:/root/.docker
# SSH directory for Linux, OSX and WSL
- ~/.ssh:/root/.ssh:z
- ~/.ssh:/root/.ssh
# For Windows without WSL, a copy will be made
# from /tmp/.ssh to ~/.ssh to fix permissions
#- ~/.ssh:/tmp/.ssh:ro
# Shell history persistence
- ~/.zsh_history:/root/.zsh_history:z
- ~/.zsh_history:/root/.zsh_history
# Git config
- ~/.gitconfig:/root/.gitconfig:z
- ~/.gitconfig:/root/.gitconfig
environment:
- TZ=
cap_add:

View File

@@ -54,8 +54,10 @@ body:
- PrivateVPN
- ProtonVPN
- PureVPN
- SlickVPN
- Surfshark
- TorGuard
- VPNSecure.me
- VPNUnlimited
- VyprVPN
- WeVPN
@@ -96,7 +98,7 @@ body:
attributes:
label: Share your logs
description: No sensitive information is logged out except when running with `LOG_LEVEL=debug`.
render: log
render: plain text
validations:
required: true
- type: textarea

5
.github/labels.yml vendored
View File

@@ -64,12 +64,17 @@
- name: ":cloud: PureVPN"
color: "cfe8d4"
description: ""
- name: ":cloud: SlickVPN"
color: "cfe8d4"
description: ""
- name: ":cloud: Surfshark"
color: "cfe8d4"
description: ""
- name: ":cloud: Torguard"
color: "cfe8d4"
description: ""
- name: ":cloud: VPNSecure.me"
color: "cfe8d4"
- name: ":cloud: VPNUnlimited"
color: "cfe8d4"
description: ""

37
.github/workflows/ci-skip.yml vendored Normal file
View File

@@ -0,0 +1,37 @@
name: No trigger file paths
on:
push:
branches:
- master
paths-ignore:
- .github/workflows/ci.yml
- cmd/**
- internal/**
- pkg/**
- .dockerignore
- .golangci.yml
- Dockerfile
- go.mod
- go.sum
pull_request:
branches:
- master
paths-ignore:
- .github/workflows/ci.yml
- cmd/**
- internal/**
- pkg/**
- .dockerignore
- .golangci.yml
- Dockerfile
- go.mod
- go.sum
jobs:
verify:
runs-on: ubuntu-latest
permissions:
actions: read
steps:
- name: No trigger path triggered for required verify workflow.
run: exit 0

View File

@@ -32,23 +32,25 @@ on:
jobs:
verify:
# Only run if it's a push event or if it's a PR from this repository, and it is not dependabot.
if: |
github.actor != 'dependabot[bot]' &&
(github.event_name == 'push' ||
github.event_name == 'release' ||
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository))
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
env:
DOCKER_BUILDKIT: "1"
steps:
- uses: actions/checkout@v2.4.0
- uses: actions/checkout@v3
- uses: reviewdog/action-misspell@v1
with:
locale: "US"
level: error
- name: Linting
run: docker build --target lint .
- name: Go mod tidy check
run: docker build --target tidy .
- name: Mocks check
run: docker build --target mocks .
- name: Build test image
run: docker build --target test -t test-container .
@@ -60,79 +62,71 @@ jobs:
-v "$(pwd)/coverage.txt:/tmp/gobuild/coverage.txt" \
test-container
- name: Code security analysis
uses: snyk/actions/golang@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
- name: Build final image
run: docker build -t final-image .
# - name: Image security analysis
# uses: snyk/actions/docker@master
# env:
# SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
# with:
# image: final-image
codeql:
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
steps:
- uses: actions/checkout@v3
- uses: github/codeql-action/init@v2
with:
languages: go
- uses: github/codeql-action/autobuild@v2
- uses: github/codeql-action/analyze@v2
publish:
# Only run if it's a push event or if it's a PR from this repository
if: |
github.event_name == 'push' ||
github.event_name == 'release' ||
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository)
needs: [verify]
github.repository == 'qdm12/gluetun' &&
(
github.event_name == 'push' ||
github.event_name == 'release' ||
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && github.actor != 'dependabot[bot]')
)
needs: [verify, codeql]
permissions:
actions: read
contents: read
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2.4.0
- uses: docker/setup-qemu-action@v1
- uses: docker/setup-buildx-action@v1
- uses: docker/login-action@v1
with:
username: qmcgaw
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Check for semver tag
id: semvercheck
run: |
if [[ ${{ github.ref }} =~ ^refs\/tags\/v0\.[0-9]+\.[0-9]+$ ]]; then
MATCH=true
else
MATCH=false
fi
if [[ ${{ github.ref }} =~ ^refs\/tags\/v[1-9]+\.[0-9]+\.[0-9]+$ ]]; then
MATCH=$MATCH_nonzero
fi
echo ::set-output name=match::$MATCH
- uses: actions/checkout@v3
# extract metadata (tags, labels) for Docker
# https://github.com/docker/metadata-action
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@v3
uses: docker/metadata-action@v4
with:
flavor: |
latest=${{ github.ref == 'refs/heads/master' }}
latest=${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }}
images: |
qmcgaw/gluetun
qmcgaw/private-internet-access
tags: |
type=ref,event=branch,enable=${{ github.ref != 'refs/heads/master' }}
type=ref,event=pr
type=ref,event=tag,enable=${{ !startsWith(steps.semvercheck.outputs.match, 'true') }}
type=semver,pattern=v{{major}}.{{minor}}.{{patch}},enable=${{ startsWith(steps.semvercheck.outputs.match, 'true') }}
type=semver,pattern=v{{major}}.{{minor}},enable=${{ startsWith(steps.semvercheck.outputs.match, 'true') }}
type=semver,pattern=v{{major}},enable=${{ startsWith(steps.semvercheck.outputs.match, 'true_nonzero') }}
type=raw,value=latest,enable=${{ !startsWith(steps.semvercheck.outputs.match, 'true') }}
type=semver,pattern=v{{major}}.{{minor}}.{{patch}}
type=semver,pattern=v{{major}}.{{minor}}
type=semver,pattern=v{{major}},enable=${{ !startsWith(github.ref, 'refs/tags/v0.') }}
type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }}
- uses: docker/setup-qemu-action@v2
- uses: docker/setup-buildx-action@v2
- uses: docker/login-action@v2
with:
username: qmcgaw
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Short commit
id: shortcommit
run: echo "::set-output name=value::$(git rev-parse --short HEAD)"
- name: Build and push final image
uses: docker/build-push-action@v2.7.0
uses: docker/build-push-action@v3.1.1
with:
platforms: linux/amd64,linux/386,linux/arm64,linux/arm/v6,linux/arm/v7,linux/ppc64le
labels: ${{ steps.meta.outputs.labels }}

View File

@@ -1,37 +0,0 @@
name: Dependabot
on:
pull_request:
branches:
- master
paths:
- .github/workflows/dependabot.yml
- cmd/**
- internal/**
- pkg/**
- .dockerignore
- .golangci.yml
- Dockerfile
- go.mod
- go.sum
jobs:
verify:
if: ${{ github.actor == 'dependabot[bot]' }}
runs-on: ubuntu-latest
env:
DOCKER_BUILDKIT: "1"
steps:
- uses: actions/checkout@v2.4.0
- name: Build test image
run: docker build --target test -t test-container .
- name: Run tests in test container
run: |
touch coverage.txt
docker run --rm \
-v "$(pwd)/coverage.txt:/tmp/gobuild/coverage.txt" \
test-container
- name: Build final image
run: docker build -t final-image .

View File

@@ -1,18 +1,22 @@
name: Docker Hub description
on:
push:
branches: [master]
branches:
- master
paths:
- README.md
- .github/workflows/dockerhub-description.yml
jobs:
dockerHubDescription:
docker-hub-description:
if: github.repository == 'qdm12/gluetun'
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
steps:
- name: Checkout
uses: actions/checkout@v2.4.0
- name: Docker Hub Description
uses: peter-evans/dockerhub-description@v2
- uses: actions/checkout@v3
- uses: peter-evans/dockerhub-description@v3
with:
username: qmcgaw
password: ${{ secrets.DOCKERHUB_PASSWORD }}

View File

@@ -1,40 +0,0 @@
name: Fork
on:
pull_request:
branches:
- master
paths:
- .github/workflows/fork.yml
- cmd/**
- internal/**
- pkg/**
- .dockerignore
- .golangci.yml
- Dockerfile
- go.mod
- go.sum
jobs:
verify:
if: github.event.pull_request.head.repo.full_name != github.repository && github.actor != 'dependabot[bot]'
runs-on: ubuntu-latest
env:
DOCKER_BUILDKIT: "1"
steps:
- uses: actions/checkout@v2.4.0
- name: Linting
run: docker build --target lint .
- name: Build test image
run: docker build --target test -t test-container .
- name: Run tests in test container
run: |
touch coverage.txt
docker run --rm \
-v "$(pwd)/coverage.txt:/tmp/gobuild/coverage.txt" \
test-container
- name: Build final image
run: docker build -t final-image .

View File

@@ -7,9 +7,11 @@ on:
- .github/workflows/labels.yml
jobs:
labeler:
permissions:
issues: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2.4.0
- uses: crazy-max/ghaction-github-labeler@v3
- uses: actions/checkout@v3
- uses: crazy-max/ghaction-github-labeler@v4
with:
yaml-file: .github/labels.yml

View File

@@ -1,15 +0,0 @@
name: Misspells
on:
pull_request:
branches: [master]
push:
branches: [master]
jobs:
misspell:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2.4.0
- uses: reviewdog/action-misspell@v1
with:
locale: "US"
level: error

View File

@@ -7,48 +7,45 @@ issues:
- path: _test\.go
linters:
- dupl
- maligned
- goerr113
- path: internal/server/
- containedctx
- path: "internal\\/server\\/.+\\.go"
linters:
- dupl
- path: internal/configuration/
- path: "internal\\/configuration\\/settings\\/.+\\.go"
linters:
- dupl
- path: internal/constants/
linters:
- dupl
- text: "exported: exported var Err*"
linters:
- revive
- text: "mnd: Magic number: 0644*"
- text: "^mnd: Magic number: 0[0-9]{3}, in <argument> detected$"
source: "^.+= os\\.OpenFile\\(.+, .+, 0[0-9]{3}\\)"
linters:
- gomnd
- text: "mnd: Magic number: 0400*"
- text: "^mnd: Magic number: 0[0-9]{3}, in <argument> detected$"
source: "^.+= os\\.MkdirAll\\(.+, 0[0-9]{3}\\)"
linters:
- gomnd
- text: "variable 'mssFix' is only used in the if-statement*"
path: "openvpnconf.go"
- linters:
- lll
source: "^//go:generate .+$"
- text: "returns interface \\(github\\.com\\/vishvananda\\/netlink\\.Link\\)"
linters:
- ifshort
- text: "variable 'auth' is only used in the if-statement*"
path: "openvpnconf.go"
linters:
- ifshort
- ireturn
linters:
enable:
# - cyclop
# - errorlint
# - ireturn
# - varnamelen
# - wrapcheck
- asasalint
- asciicheck
- bidichk
- bodyclose
- containedctx
- decorder
- dogsled
- dupl
- durationcheck
- errchkjson
- errname
- execinquery
- exhaustive
- exportloopref
- forcetypeassert
@@ -67,9 +64,12 @@ linters:
- gomoddirectives
- goprintffuncname
- gosec
- ifshort
- grouper
- importas
- interfacebloat
- ireturn
- lll
- maintidx
- makezero
- misspell
- nakedret
@@ -78,10 +78,11 @@ linters:
- nilnil
- noctx
- nolintlint
- nosprintfhostport
- prealloc
- predeclared
- predeclared
- promlinter
- reassign
- revive
- rowserrcheck
- sqlclosecheck
@@ -90,6 +91,7 @@ linters:
- tparallel
- unconvert
- unparam
- usestdlibvars
- wastedassign
- whitespace

25
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,25 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Update a VPN provider servers data",
"type": "go",
"request": "launch",
"mode": "debug",
"program": "cmd/gluetun/main.go",
"args": [
"update",
"-enduser",
"-providers",
"${input:provider}"
],
}
],
"inputs": [
{
"id": "provider",
"type": "promptString",
"description": "Please enter a provider (or comma separated list of providers)",
}
]
}

View File

@@ -1,18 +1,22 @@
ARG ALPINE_VERSION=3.15
ARG GO_ALPINE_VERSION=3.15
ARG ALPINE_VERSION=3.16
ARG GO_ALPINE_VERSION=3.16
ARG GO_VERSION=1.17
ARG XCPUTRANSLATE_VERSION=v0.6.0
ARG GOLANGCI_LINT_VERSION=v1.43.0
ARG GOLANGCI_LINT_VERSION=v1.49.0
ARG MOCKGEN_VERSION=v1.6.0
ARG BUILDPLATFORM=linux/amd64
FROM --platform=${BUILDPLATFORM} qmcgaw/xcputranslate:${XCPUTRANSLATE_VERSION} AS xcputranslate
FROM --platform=${BUILDPLATFORM} qmcgaw/binpot:golangci-lint-${GOLANGCI_LINT_VERSION} AS golangci-lint
FROM --platform=${BUILDPLATFORM} qmcgaw/binpot:mockgen-${MOCKGEN_VERSION} AS mockgen
FROM --platform=${BUILDPLATFORM} golang:${GO_VERSION}-alpine${GO_ALPINE_VERSION} AS base
COPY --from=xcputranslate /xcputranslate /usr/local/bin/xcputranslate
RUN apk --update add git g++
# Note: findutils needed to have xargs support `-d` flag for mocks stage.
RUN apk --update add git g++ findutils
ENV CGO_ENABLED=0
COPY --from=golangci-lint /bin /go/bin/golangci-lint
COPY --from=mockgen /bin /go/bin/mockgen
WORKDIR /tmp/gobuild
COPY go.mod go.sum ./
RUN go mod download
@@ -30,14 +34,17 @@ FROM --platform=${BUILDPLATFORM} base AS lint
COPY .golangci.yml ./
RUN golangci-lint run --timeout=10m
FROM --platform=${BUILDPLATFORM} base AS tidy
FROM --platform=${BUILDPLATFORM} base AS mocks
RUN git init && \
git config user.email ci@localhost && \
git config user.name ci && \
git add -A && git commit -m ci && \
sed -i '/\/\/ indirect/d' go.mod && \
go mod tidy && \
git diff --exit-code -- go.mod
git config core.fileMode false && \
git add -A && \
git commit -m "snapshot" && \
grep -lr -E '^// Code generated by MockGen\. DO NOT EDIT\.$' . | xargs -r -d '\n' rm && \
go generate -run "mockgen" ./... && \
git diff --exit-code && \
rm -rf .git/
FROM --platform=${BUILDPLATFORM} base AS build
ARG TARGETPLATFORM
@@ -66,8 +73,12 @@ LABEL \
org.opencontainers.image.source="https://github.com/qdm12/gluetun" \
org.opencontainers.image.title="VPN swiss-knife like client for multiple VPN providers" \
org.opencontainers.image.description="VPN swiss-knife like client to tunnel to multiple VPN servers using OpenVPN, IPtables, DNS over TLS, Shadowsocks, an HTTP proxy and Alpine Linux"
ENV VPNSP=pia \
ENV VPN_SERVICE_PROVIDER=pia \
VPN_TYPE=openvpn \
# Common VPN options
VPN_ENDPOINT_IP= \
VPN_ENDPOINT_PORT= \
VPN_INTERFACE=tun0 \
# OpenVPN
OPENVPN_PROTOCOL=udp \
OPENVPN_USER= \
@@ -77,45 +88,48 @@ ENV VPNSP=pia \
OPENVPN_VERSION=2.5 \
OPENVPN_VERBOSITY=1 \
OPENVPN_FLAGS= \
OPENVPN_CIPHER= \
OPENVPN_CIPHERS= \
OPENVPN_AUTH= \
OPENVPN_ROOT=yes \
OPENVPN_TARGET_IP= \
OPENVPN_PROCESS_USER= \
OPENVPN_IPV6=off \
OPENVPN_CUSTOM_CONFIG= \
OPENVPN_INTERFACE=tun0 \
OPENVPN_PORT= \
# Wireguard
WIREGUARD_PRIVATE_KEY= \
WIREGUARD_PRESHARED_KEY= \
WIREGUARD_PUBLIC_KEY= \
WIREGUARD_ADDRESS= \
WIREGUARD_ENDPOINT_IP= \
WIREGUARD_ENDPOINT_PORT= \
WIREGUARD_INTERFACE=wg0 \
WIREGUARD_ADDRESSES= \
# VPN server filtering
REGION= \
COUNTRY= \
CITY= \
SERVER_HOSTNAME= \
SERVER_REGIONS= \
SERVER_COUNTRIES= \
SERVER_CITIES= \
SERVER_HOSTNAMES= \
# # Mullvad only:
ISP= \
OWNED=no \
OWNED_ONLY=no \
# # Private Internet Access only:
PIA_ENCRYPTION= \
PORT_FORWARDING=off \
PORT_FORWARDING_STATUS_FILE="/tmp/gluetun/forwarded_port" \
PRIVATE_INTERNET_ACCESS_OPENVPN_ENCRYPTION_PRESET= \
PRIVATE_INTERNET_ACCESS_VPN_PORT_FORWARDING=off \
PRIVATE_INTERNET_ACCESS_VPN_PORT_FORWARDING_STATUS_FILE="/tmp/gluetun/forwarded_port" \
# # Cyberghost only:
OPENVPN_CERT= \
OPENVPN_KEY= \
OPENVPN_CLIENTCRT_SECRETFILE=/run/secrets/openvpn_clientcrt \
OPENVPN_CLIENTKEY_SECRETFILE=/run/secrets/openvpn_clientkey \
# # VPNSecure only:
OPENVPN_ENCRYPTED_KEY= \
OPENVPN_ENCRYPTED_KEY_SECRETFILE=/run/secrets/openvpn_encrypted_key \
OPENVPN_KEY_PASSPHRASE= \
OPENVPN_KEY_PASSPHRASE_SECRETFILE=/run/secrets/openvpn_key_passphrase \
# # Nordvpn only:
SERVER_NUMBER= \
# # PIA and ProtonVPN only:
SERVER_NAME= \
# # PIA only:
SERVER_NAMES= \
# # ProtonVPN only:
FREE_ONLY= \
# # Surfshark only:
MULTIHOP_ONLY= \
# # VPN Secure only:
PREMIUM_ONLY= \
# Firewall
FIREWALL=on \
FIREWALL_VPN_INPUT_PORTS= \
@@ -126,7 +140,7 @@ ENV VPNSP=pia \
LOG_LEVEL=info \
# Health
HEALTH_SERVER_ADDRESS=127.0.0.1:9999 \
HEALTH_ADDRESS_TO_PING=github.com \
HEALTH_TARGET_ADDRESS=cloudflare.com:443 \
HEALTH_VPN_DURATION_INITIAL=6s \
HEALTH_VPN_DURATION_ADDITION=5s \
# DNS over TLS
@@ -143,7 +157,7 @@ ENV VPNSP=pia \
BLOCK_ADS=off \
UNBLOCK= \
DNS_UPDATE_PERIOD=24h \
DNS_PLAINTEXT_ADDRESS=127.0.0.1 \
DNS_ADDRESS=127.0.0.1 \
DNS_KEEP_NAMESERVER=off \
# HTTP proxy
HTTPPROXY= \
@@ -160,11 +174,20 @@ ENV VPNSP=pia \
SHADOWSOCKS_PASSWORD= \
SHADOWSOCKS_PASSWORD_SECRETFILE=/run/secrets/shadowsocks_password \
SHADOWSOCKS_CIPHER=chacha20-ietf-poly1305 \
# Control server
HTTP_CONTROL_SERVER_ADDRESS=":8000" \
# Server data updater
UPDATER_PERIOD=0 \
UPDATER_MIN_RATIO=0.8 \
UPDATER_VPN_SERVICE_PROVIDERS= \
# Public IP
PUBLICIP_FILE="/tmp/gluetun/ip" \
PUBLICIP_PERIOD=12h \
# Pprof
PPROF_ENABLED=no \
PPROF_BLOCK_PROFILE_RATE=0 \
PPROF_MUTEX_PROFILE_RATE=0 \
PPROF_HTTP_SERVER_ADDRESS=":6060" \
# Extras
VERSION_INFORMATION=on \
TZ= \
@@ -175,7 +198,7 @@ EXPOSE 8000/tcp 8888/tcp 8388/tcp 8388/udp
HEALTHCHECK --interval=5s --timeout=5s --start-period=10s --retries=1 CMD /gluetun-entrypoint healthcheck
ARG TARGETPLATFORM
RUN apk add --no-cache --update -l apk-tools && \
apk add --no-cache --update -X "https://dl-cdn.alpinelinux.org/alpine/v3.12/main" openvpn==2.4.11-r0 && \
apk add --no-cache --update -X "https://dl-cdn.alpinelinux.org/alpine/v3.12/main" openvpn==2.4.12-r0 && \
mv /usr/sbin/openvpn /usr/sbin/openvpn2.4 && \
apk del openvpn && \
apk add --no-cache --update openvpn ca-certificates iptables ip6tables unbound tzdata && \

View File

@@ -1,11 +1,6 @@
# Gluetun VPN client
*Lightweight swiss-knife-like VPN client to tunnel to Cyberghost, ExpressVPN, FastestVPN,
HideMyAss, IPVanish, IVPN, Mullvad, NordVPN, Perfect Privacy, Privado, Private Internet Access, PrivateVPN,
ProtonVPN, PureVPN, Surfshark, TorGuard, VPNUnlimited, VyprVPN, WeVPN and Windscribe VPN servers
using Go, OpenVPN or Wireguard, iptables, DNS over TLS, ShadowSocks and an HTTP proxy*
**ANNOUNCEMENT**: Large settings refactor merged on 2022-06-01, please file issues if you find any problem!
Lightweight swiss-knife-like VPN client to multiple VPN service providers
![Title image](https://raw.githubusercontent.com/qdm12/gluetun/master/title.svg)
@@ -53,6 +48,7 @@ using Go, OpenVPN or Wireguard, iptables, DNS over TLS, ShadowSocks and an HTTP
- Sponsor me on [github.com/sponsors/qdm12](https://github.com/sponsors/qdm12)
- Donate to [paypal.me/qmcgaw](https://www.paypal.me/qmcgaw)
- Drop me [an email](mailto:quentin.mcgaw@gmail.com)
- **Want to add a VPN provider?** check [Development](https://github.com/qdm12/gluetun/wiki/Development) and [Add a provider](https://github.com/qdm12/gluetun/wiki/Add-a-provider)
- Video:
[![Video Gif](https://i.imgur.com/CetWunc.gif)](https://youtu.be/0F6I03LQcI4)
@@ -61,8 +57,8 @@ using Go, OpenVPN or Wireguard, iptables, DNS over TLS, ShadowSocks and an HTTP
## Features
- Based on Alpine 3.15 for a small Docker image of 29MB
- Supports: **Cyberghost**, **ExpressVPN**, **FastestVPN**, **HideMyAss**, **IPVanish**, **IVPN**, **Mullvad**, **NordVPN**, **Perfect Privacy**, **Privado**, **Private Internet Access**, **PrivateVPN**, **ProtonVPN**, **PureVPN**, **Surfshark**, **TorGuard**, **VPNUnlimited**, **Vyprvpn**, **WeVPN**, **Windscribe** servers
- Based on Alpine 3.16 for a small Docker image of 29MB
- Supports: **Cyberghost**, **ExpressVPN**, **FastestVPN**, **HideMyAss**, **IPVanish**, **IVPN**, **Mullvad**, **NordVPN**, **Perfect Privacy**, **Privado**, **Private Internet Access**, **PrivateVPN**, **ProtonVPN**, **PureVPN**, **SlickVPN**, **Surfshark**, **TorGuard**, **VPNSecure.me**, **VPNUnlimited**, **Vyprvpn**, **WeVPN**, **Windscribe** servers
- Supports OpenVPN for all providers listed
- Supports Wireguard both kernelspace and userspace
- For **Mullvad**, **Ivpn** and **Windscribe**
@@ -102,6 +98,8 @@ services:
# line above must be uncommented to allow external containers to connect. See https://github.com/qdm12/gluetun/wiki/Connect-a-container-to-gluetun#external-container-to-gluetun
cap_add:
- NET_ADMIN
devices:
- /dev/net/tun:/dev/net/tun
ports:
- 8888:8888/tcp # HTTP proxy
- 8388:8388/tcp # Shadowsocks
@@ -110,14 +108,14 @@ services:
- /yourpath:/gluetun
environment:
# See https://github.com/qdm12/gluetun/wiki
- VPNSP=ivpn
- VPN_SERVICE_PROVIDER=ivpn
- VPN_TYPE=openvpn
# OpenVPN:
- OPENVPN_USER=
- OPENVPN_PASSWORD=
# Wireguard:
# - WIREGUARD_PRIVATE_KEY=wOEI9rqqbDwnN8/Bpp22sVz48T71vJ4fYmFWujulwUU=
# - WIREGUARD_ADDRESS=10.64.222.21/32
# - WIREGUARD_ADDRESSES=10.64.222.21/32
# Timezone for accurate log times
- TZ=
```

View File

@@ -29,22 +29,28 @@ import (
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/netlink"
"github.com/qdm12/gluetun/internal/openvpn"
"github.com/qdm12/gluetun/internal/openvpn/extract"
"github.com/qdm12/gluetun/internal/portforward"
"github.com/qdm12/gluetun/internal/pprof"
"github.com/qdm12/gluetun/internal/provider"
"github.com/qdm12/gluetun/internal/publicip"
"github.com/qdm12/gluetun/internal/publicip/ipinfo"
"github.com/qdm12/gluetun/internal/routing"
"github.com/qdm12/gluetun/internal/server"
"github.com/qdm12/gluetun/internal/shadowsocks"
"github.com/qdm12/gluetun/internal/storage"
"github.com/qdm12/gluetun/internal/tun"
"github.com/qdm12/gluetun/internal/updater"
updater "github.com/qdm12/gluetun/internal/updater/loop"
"github.com/qdm12/gluetun/internal/updater/resolver"
"github.com/qdm12/gluetun/internal/updater/unzip"
"github.com/qdm12/gluetun/internal/vpn"
"github.com/qdm12/golibs/command"
"github.com/qdm12/golibs/logging"
"github.com/qdm12/goshutdown"
"github.com/qdm12/goshutdown/goroutine"
"github.com/qdm12/goshutdown/group"
"github.com/qdm12/goshutdown/order"
"github.com/qdm12/gosplash"
"github.com/qdm12/log"
"github.com/qdm12/updated/pkg/dnscrypto"
)
@@ -55,11 +61,6 @@ var (
created = "an unknown date"
)
var (
errSetupRouting = errors.New("cannot setup routing")
errCreateUser = errors.New("cannot create user")
)
func main() {
buildInfo := models.BuildInformation{
Version: version,
@@ -68,12 +69,11 @@ func main() {
}
background := context.Background()
signalCtx, stop := signal.NotifyContext(background, syscall.SIGINT, syscall.SIGTERM, os.Interrupt)
signalCh := make(chan os.Signal, 1)
signal.Notify(signalCh, os.Interrupt, syscall.SIGTERM)
ctx, cancel := context.WithCancel(background)
logger := logging.New(logging.Settings{
Level: logging.LevelInfo,
})
logger := log.New(log.SetLevel(log.LevelInfo))
args := os.Args
tun := tun.New()
@@ -92,13 +92,11 @@ func main() {
}()
select {
case <-signalCtx.Done():
stop()
case signal := <-signalCh:
fmt.Println("")
logger.Warn("Caught OS signal, shutting down")
logger.Warn("Caught OS signal " + signal.String() + ", shutting down")
cancel()
case err := <-errorCh:
stop()
close(errorCh)
if err == nil { // expected exit such as healthcheck
os.Exit(0)
@@ -117,6 +115,8 @@ func main() {
logger.Info("Shutdown successful")
case <-timer.C:
logger.Warn("Shutdown timed out")
case signal := <-signalCh:
logger.Warn("Caught OS signal " + signal.String() + ", forcing shut down")
}
os.Exit(1)
@@ -126,11 +126,11 @@ var (
errCommandUnknown = errors.New("command is unknown")
)
//nolint:gocognit,gocyclo
//nolint:gocognit,gocyclo,maintidx
func _main(ctx context.Context, buildInfo models.BuildInformation,
args []string, logger logging.ParentLogger, source sources.Source,
tun tun.Interface, netLinker netlink.NetLinker, cmder command.RunStarter,
cli cli.CLIer) error {
args []string, logger log.LoggerInterface, source sources.Source,
tun Tun, netLinker netLinker, cmder command.RunStarter,
cli clier) error {
if len(args) > 1 { // cli operation
switch args[1] {
case "healthcheck":
@@ -174,21 +174,62 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
return err
}
// Note: no need to validate minimal settings for the firewall:
// - global log level is parsed from source
// - firewall Debug and Enabled are booleans parsed from source
logger.Patch(log.SetLevel(*allSettings.Log.Level))
routingLogger := logger.New(log.SetComponent("routing"))
if *allSettings.Firewall.Debug { // To remove in v4
routingLogger.Patch(log.SetLevel(log.LevelDebug))
}
routingConf := routing.New(netLinker, routingLogger)
defaultRoutes, err := routingConf.DefaultRoutes()
if err != nil {
return err
}
localNetworks, err := routingConf.LocalNetworks()
if err != nil {
return err
}
firewallLogger := logger.New(log.SetComponent("firewall"))
if *allSettings.Firewall.Debug { // To remove in v4
firewallLogger.Patch(log.SetLevel(log.LevelDebug))
}
firewallConf, err := firewall.NewConfig(ctx, firewallLogger, cmder,
defaultRoutes, localNetworks)
if err != nil {
return err
}
if *allSettings.Firewall.Enabled {
err = firewallConf.SetEnabled(ctx, true)
if err != nil {
return err
}
}
// TODO run this in a loop or in openvpn to reload from file without restarting
storageLogger := logger.NewChild(logging.Settings{Prefix: "storage: "})
storageLogger := logger.New(log.SetComponent("storage"))
storage, err := storage.New(storageLogger, constants.ServersData)
if err != nil {
return err
}
allServers := storage.GetServers()
err = allSettings.Validate(allServers)
err = allSettings.Validate(storage)
if err != nil {
return err
}
logger.PatchLevel(*allSettings.Log.Level)
allSettings.Pprof.HTTPServer.Logger = logger.New(log.SetComponent("pprof"))
pprofServer, err := pprof.New(allSettings.Pprof)
if err != nil {
return fmt.Errorf("cannot create Pprof server: %w", err)
}
puid, pgid := int(*allSettings.System.PUID), int(*allSettings.System.PGID)
@@ -197,7 +238,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
// Create configurators
alpineConf := alpine.New()
ovpnConf := openvpn.New(
logger.NewChild(logging.Settings{Prefix: "openvpn configurator: "}),
logger.New(log.SetComponent("openvpn configurator")),
cmder, puid, pgid)
dnsCrypto := dnscrypto.New(httpClient, "", "")
const cacertsPath = "/etc/ssl/certs/ca-certificates.crt"
@@ -229,7 +270,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
const defaultUsername = "nonrootuser"
nonRootUsername, err := alpineConf.CreateUser(defaultUsername, puid)
if err != nil {
return fmt.Errorf("%w: %s", errCreateUser, err)
return fmt.Errorf("cannot create user: %w", err)
}
if nonRootUsername != defaultUsername {
logger.Info("using existing username " + nonRootUsername + " corresponding to user id " + fmt.Sprint(puid))
@@ -237,54 +278,22 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
// set it for Unbound
// TODO remove this when migrating to qdm12/dns v2
allSettings.DNS.DoT.Unbound.Username = nonRootUsername
allSettings.VPN.OpenVPN.ProcUser = nonRootUsername
allSettings.VPN.OpenVPN.ProcessUser = nonRootUsername
if err := os.Chown("/etc/unbound", puid, pgid); err != nil {
return err
}
firewallLogLevel := *allSettings.Log.Level
if *allSettings.Firewall.Debug {
firewallLogLevel = logging.LevelDebug
}
routingLogger := logger.NewChild(logging.Settings{
Prefix: "routing: ",
Level: firewallLogLevel,
})
routingConf := routing.New(netLinker, routingLogger)
defaultInterface, defaultGateway, err := routingConf.DefaultRoute()
if err != nil {
return err
}
localNetworks, err := routingConf.LocalNetworks()
if err != nil {
return err
}
defaultIP, err := routingConf.DefaultIP()
if err != nil {
return err
}
firewallLogger := logger.NewChild(logging.Settings{
Prefix: "firewall: ",
Level: firewallLogLevel,
})
firewallConf := firewall.NewConfig(firewallLogger, cmder,
defaultInterface, defaultGateway, localNetworks, defaultIP)
if err := routingConf.Setup(); err != nil {
if strings.Contains(err.Error(), "operation not permitted") {
logger.Warn("💡 Tip: Are you passing NET_ADMIN capability to gluetun?")
}
return fmt.Errorf("%w: %s", errSetupRouting, err)
return fmt.Errorf("cannot setup routing: %w", err)
}
defer func() {
logger.Info("routing cleanup...")
routingLogger.Info("routing cleanup...")
if err := routingConf.TearDown(); err != nil {
logger.Error("cannot teardown routing: " + err.Error())
routingLogger.Error("cannot teardown routing: " + err.Error())
}
}()
@@ -295,25 +304,21 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
return err
}
if err := tun.Check(constants.TunnelDevice); err != nil {
const tunDevice = "/dev/net/tun"
if err := tun.Check(tunDevice); err != nil {
logger.Info(err.Error() + "; creating it...")
err = tun.Create(constants.TunnelDevice)
if err != nil {
return err
}
}
if *allSettings.Firewall.Enabled {
err := firewallConf.SetEnabled(ctx, true) // disabled by default
err = tun.Create(tunDevice)
if err != nil {
return err
}
}
for _, port := range allSettings.Firewall.InputPorts {
err = firewallConf.SetAllowedPort(ctx, port, defaultInterface)
if err != nil {
return err
for _, defaultRoute := range defaultRoutes {
err = firewallConf.SetAllowedPort(ctx, port, defaultRoute.NetInterface)
if err != nil {
return err
}
}
} // TODO move inside firewall?
@@ -334,14 +339,20 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
tickersGroupHandler := goshutdown.NewGroupHandler("tickers", defaultGroupOptions...)
otherGroupHandler := goshutdown.NewGroupHandler("other", defaultGroupOptions...)
portForwardLogger := logger.NewChild(logging.Settings{Prefix: "port forwarding: "})
pprofReady := make(chan struct{})
pprofHandler, pprofCtx, pprofDone := goshutdown.NewGoRoutineHandler("pprof server")
go pprofServer.Run(pprofCtx, pprofReady, pprofDone)
otherGroupHandler.Add(pprofHandler)
<-pprofReady
portForwardLogger := logger.New(log.SetComponent("port forwarding"))
portForwardLooper := portforward.NewLoop(allSettings.VPN.Provider.PortForwarding,
httpClient, firewallConf, portForwardLogger)
httpClient, firewallConf, portForwardLogger, puid, pgid)
portForwardHandler, portForwardCtx, portForwardDone := goshutdown.NewGoRoutineHandler(
"port forwarding", goroutine.OptionTimeout(time.Second))
go portForwardLooper.Run(portForwardCtx, portForwardDone)
unboundLogger := logger.NewChild(logging.Settings{Prefix: "dns over tls: "})
unboundLogger := logger.New(log.SetComponent("dns over tls"))
unboundLooper := dns.NewLoop(dnsConf, allSettings.DNS, httpClient,
unboundLogger)
dnsHandler, dnsCtx, dnsDone := goshutdown.NewGoRoutineHandler(
@@ -355,8 +366,9 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
go unboundLooper.RunRestartTicker(dnsTickerCtx, dnsTickerDone)
controlGroupHandler.Add(dnsTickerHandler)
publicIPLooper := publicip.NewLoop(httpClient,
logger.NewChild(logging.Settings{Prefix: "ip getter: "}),
ipFetcher := ipinfo.New(httpClient)
publicIPLooper := publicip.NewLoop(ipFetcher,
logger.New(log.SetComponent("ip getter")),
allSettings.PublicIP, puid, pgid)
pubIPHandler, pubIPCtx, pubIPDone := goshutdown.NewGoRoutineHandler(
"public IP", goroutine.OptionTimeout(defaultShutdownTimeout))
@@ -368,18 +380,25 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
go publicIPLooper.RunRestartTicker(pubIPTickerCtx, pubIPTickerDone)
tickersGroupHandler.Add(pubIPTickerHandler)
vpnLogger := logger.NewChild(logging.Settings{Prefix: "vpn: "})
updaterLogger := logger.New(log.SetComponent("updater"))
unzipper := unzip.New(httpClient)
parallelResolver := resolver.NewParallelResolver(allSettings.Updater.DNSAddress)
openvpnFileExtractor := extract.New()
providers := provider.NewProviders(storage, time.Now, updaterLogger,
httpClient, unzipper, parallelResolver, ipFetcher, openvpnFileExtractor)
vpnLogger := logger.New(log.SetComponent("vpn"))
vpnLooper := vpn.NewLoop(allSettings.VPN, allSettings.Firewall.VPNInputPorts,
allServers, ovpnConf, netLinker, firewallConf, routingConf, portForwardLooper,
providers, storage, ovpnConf, netLinker, firewallConf, routingConf, portForwardLooper,
cmder, publicIPLooper, unboundLooper, vpnLogger, httpClient,
buildInfo, *allSettings.Version.Enabled)
vpnHandler, vpnCtx, vpnDone := goshutdown.NewGoRoutineHandler(
"vpn", goroutine.OptionTimeout(time.Second))
go vpnLooper.Run(vpnCtx, vpnDone)
updaterLooper := updater.NewLooper(allSettings.Updater,
allServers, storage, vpnLooper.SetServers, httpClient,
logger.NewChild(logging.Settings{Prefix: "updater: "}))
updaterLooper := updater.NewLoop(allSettings.Updater,
providers, storage, httpClient, updaterLogger)
updaterHandler, updaterCtx, updaterDone := goshutdown.NewGoRoutineHandler(
"updater", goroutine.OptionTimeout(defaultShutdownTimeout))
// wait for updaterLooper.Restart() or its ticket launched with RunRestartTicker
@@ -392,31 +411,36 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
controlGroupHandler.Add(updaterTickerHandler)
httpProxyLooper := httpproxy.NewLoop(
logger.NewChild(logging.Settings{Prefix: "http proxy: "}),
logger.New(log.SetComponent("http proxy")),
allSettings.HTTPProxy)
httpProxyHandler, httpProxyCtx, httpProxyDone := goshutdown.NewGoRoutineHandler(
"http proxy", goroutine.OptionTimeout(defaultShutdownTimeout))
go httpProxyLooper.Run(httpProxyCtx, httpProxyDone)
otherGroupHandler.Add(httpProxyHandler)
shadowsocksLooper := shadowsocks.NewLooper(allSettings.Shadowsocks,
logger.NewChild(logging.Settings{Prefix: "shadowsocks: "}))
shadowsocksLooper := shadowsocks.NewLoop(allSettings.Shadowsocks,
logger.New(log.SetComponent("shadowsocks")))
shadowsocksHandler, shadowsocksCtx, shadowsocksDone := goshutdown.NewGoRoutineHandler(
"shadowsocks proxy", goroutine.OptionTimeout(defaultShutdownTimeout))
go shadowsocksLooper.Run(shadowsocksCtx, shadowsocksDone)
otherGroupHandler.Add(shadowsocksHandler)
controlServerAddress := fmt.Sprintf(":%d", *allSettings.ControlServer.Port)
controlServerAddress := *allSettings.ControlServer.Address
controlServerLogging := *allSettings.ControlServer.Log
httpServerHandler, httpServerCtx, httpServerDone := goshutdown.NewGoRoutineHandler(
"http server", goroutine.OptionTimeout(defaultShutdownTimeout))
httpServer := server.New(httpServerCtx, controlServerAddress, controlServerLogging,
logger.NewChild(logging.Settings{Prefix: "http server: "}),
buildInfo, vpnLooper, portForwardLooper, unboundLooper, updaterLooper, publicIPLooper)
go httpServer.Run(httpServerCtx, httpServerDone)
httpServer, err := server.New(httpServerCtx, controlServerAddress, controlServerLogging,
logger.New(log.SetComponent("http server")),
buildInfo, vpnLooper, portForwardLooper, unboundLooper, updaterLooper, publicIPLooper, storage)
if err != nil {
return fmt.Errorf("cannot setup control server: %w", err)
}
httpServerReady := make(chan struct{})
go httpServer.Run(httpServerCtx, httpServerReady, httpServerDone)
<-httpServerReady
controlGroupHandler.Add(httpServerHandler)
healthLogger := logger.NewChild(logging.Settings{Prefix: "healthcheck: "})
healthLogger := logger.New(log.SetComponent("healthcheck"))
healthcheckServer := healthcheck.NewServer(allSettings.Health, healthLogger, vpnLooper)
healthServerHandler, healthServerCtx, healthServerDone := goshutdown.NewGoRoutineHandler(
"HTTP health server", goroutine.OptionTimeout(defaultShutdownTimeout))
@@ -463,3 +487,54 @@ func printVersions(ctx context.Context, logger infoer,
return nil
}
type netLinker interface {
Addresser
Router
Ruler
Linker
IsWireguardSupported() (ok bool, err error)
}
type Addresser interface {
AddrList(link netlink.Link, family int) (
addresses []netlink.Addr, err error)
AddrAdd(link netlink.Link, addr *netlink.Addr) error
}
type Router interface {
RouteList(link netlink.Link, family int) (
routes []netlink.Route, err error)
RouteAdd(route *netlink.Route) error
RouteDel(route *netlink.Route) error
RouteReplace(route *netlink.Route) error
}
type Ruler interface {
RuleList(family int) (rules []netlink.Rule, err error)
RuleAdd(rule *netlink.Rule) error
RuleDel(rule *netlink.Rule) error
}
type Linker interface {
LinkList() (links []netlink.Link, err error)
LinkByName(name string) (link netlink.Link, err error)
LinkByIndex(index int) (link netlink.Link, err error)
LinkAdd(link netlink.Link) (err error)
LinkDel(link netlink.Link) (err error)
LinkSetUp(link netlink.Link) (err error)
LinkSetDown(link netlink.Link) (err error)
}
type clier interface {
ClientKey(args []string) error
FormatServers(args []string) error
OpenvpnConfig(logger cli.OpenvpnConfigLogger, source sources.Source) error
HealthCheck(ctx context.Context, source sources.Source, warner cli.Warner) error
Update(ctx context.Context, args []string, logger cli.UpdaterLogger) error
}
type Tun interface {
Check(tunDevice string) error
Create(tunDevice string) error
}

15
go.mod
View File

@@ -3,9 +3,8 @@ module github.com/qdm12/gluetun
go 1.17
require (
github.com/breml/rootcerts v0.2.1
github.com/breml/rootcerts v0.2.6
github.com/fatih/color v1.13.0
github.com/go-ping/ping v0.0.0-20210911151512-381826476871
github.com/golang/mock v1.6.0
github.com/qdm12/dns v1.11.0
github.com/qdm12/golibs v0.0.0-20210822203818-5c568b0777b6
@@ -13,11 +12,14 @@ require (
github.com/qdm12/gosplash v0.1.0
github.com/qdm12/gotree v0.2.0
github.com/qdm12/govalid v0.1.0
github.com/qdm12/log v0.1.0
github.com/qdm12/ss-server v0.4.0
github.com/qdm12/updated v0.0.0-20210603204757-205acfe6937e
github.com/stretchr/testify v1.7.0
github.com/stretchr/testify v1.8.0
github.com/vishvananda/netlink v1.1.1-0.20211129163951-9ada19101fc5
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c
golang.org/x/text v0.3.7
golang.zx2c4.com/wireguard v0.0.0-20210805125648-3957e9b9dd19
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20210803171230-4253848d036c
inet.af/netaddr v0.0.0-20210718074554-06ca8145d722
@@ -26,7 +28,6 @@ require (
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/google/go-cmp v0.5.5 // indirect
github.com/google/uuid v1.2.0 // indirect
github.com/josharian/native v0.0.0-20200817173448-b6b71def0850 // indirect
github.com/mattn/go-colorable v0.1.9 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
@@ -38,9 +39,7 @@ require (
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae // indirect
go4.org/intern v0.0.0-20210108033219-3eb7198706b2 // indirect
go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222180813-1025295fd063 // indirect
go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760 // indirect
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect
golang.org/x/net v0.0.0-20210504132125-bbd867fde50d // indirect
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

28
go.sum
View File

@@ -4,8 +4,8 @@ github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/g
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/breml/rootcerts v0.2.1 h1:GZMVDXOs945764NFck0vtHSjktKYubOFM0kjf5HAuwc=
github.com/breml/rootcerts v0.2.1/go.mod h1:24FDtzYMpqIeYC7QzaE8VPRQaFZU5TIUDlyk8qwjD88=
github.com/breml/rootcerts v0.2.6 h1:CdPczjzItec+wopLoDsBAFcLEai2q7Yayfg/94/q/2E=
github.com/breml/rootcerts v0.2.6/go.mod h1:24FDtzYMpqIeYC7QzaE8VPRQaFZU5TIUDlyk8qwjD88=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -32,8 +32,6 @@ github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsd
github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=
github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
github.com/go-openapi/validate v0.17.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4=
github.com/go-ping/ping v0.0.0-20210911151512-381826476871 h1:wtjTfjwAR/BYYMJ+QOLI/3J/qGEI0fgrkZvgsEWK2/Q=
github.com/go-ping/ping v0.0.0-20210911151512-381826476871/go.mod h1:xIFjORFzTxqIV/tDVGO4eDy/bLuSyawEeojSm3GfRGk=
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
@@ -47,8 +45,6 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gotify/go-api-client/v2 v2.0.4/go.mod h1:VKiah/UK20bXsr0JObE1eBVLW44zbBouzjuri9iwjFU=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
@@ -119,6 +115,8 @@ 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.1.0 h1:UIFVmuaAg0Q+h0GeyfcFEZ5sQ5KJPvRQwycC1/cqDN8=
github.com/qdm12/govalid v0.1.0/go.mod h1:CyS/OEQdOvunBgrtIsW93fjd4jBkwZPBjGSpxq3NwA4=
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.4.0 h1:lMMYfDGc9P86Lyvd3+p8lK4hhgHUKDzjZC91FqJYkDU=
github.com/qdm12/ss-server v0.4.0/go.mod h1:AY0p4huvPUPW+/CiWsJcDgT6sneDryk26VXSccPNCxY=
github.com/qdm12/updated v0.0.0-20210603204757-205acfe6937e h1:4q+uFLawkaQRq3yARYLsjJPZd2wYwxn4g6G/5v0xW1g=
@@ -129,11 +127,14 @@ github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAm
github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/vishvananda/netlink v1.1.1-0.20211129163951-9ada19101fc5 h1:b/k/BVWzWRS5v6AB0gf2ckFSbFsHN5jR0HoNso1pN+w=
github.com/vishvananda/netlink v1.1.1-0.20211129163951-9ada19101fc5/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae h1:4hwBBUfQCFe3Cym0ZtKyq7L16eZUtYKs+BaHDN6mAns=
@@ -145,8 +146,9 @@ github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1
go4.org/intern v0.0.0-20210108033219-3eb7198706b2 h1:VFTf+jjIgsldaz/Mr00VaCSswHJrI2hIjQygE/W4IMg=
go4.org/intern v0.0.0-20210108033219-3eb7198706b2/go.mod h1:vLqJ+12kCw61iCWsPto0EOHhBS+o4rO5VIucbc9g2Cc=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222175341-b30ae309168e/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222180813-1025295fd063 h1:1tk03FUNpulq2cuWpXZWj649rwJpk0d20rxWiopKRmc=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222180813-1025295fd063/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760 h1:FyBZqvoA/jbNzuAWLQE2kG820zMAkcilx6BMjGbL/E4=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@@ -176,10 +178,10 @@ golang.org/x/net v0.0.0-20201216054612-986b41b23924/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210504132125-bbd867fde50d h1:nTDGCTeAu2LhcsHTRzjyIUbZHCJ4QePArsm27Hka0UM=
golang.org/x/net v0.0.0-20210504132125-bbd867fde50d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8 h1:/6y1LfuqNuQdHAm0jjtPtgRcxIxjVZgm5OTu8/QhZvk=
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
@@ -210,7 +212,6 @@ golang.org/x/sys v0.0.0-20210123111255-9b0068b26619/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210216163648-f7da38b97c65/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210309040221-94ec62e08169/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -225,6 +226,8 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
@@ -250,8 +253,9 @@ gopkg.in/src-d/go-git.v4 v4.13.1/go.mod h1:nx5NYcxdKxq5fpltdHnPa2Exj4Sx0EclMWZQb
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
inet.af/netaddr v0.0.0-20210511181906-37180328850c/go.mod h1:z0nx+Dh+7N7CC8V5ayHtHGpZpxLQZZxkIaaz6HN65Ls=
inet.af/netaddr v0.0.0-20210718074554-06ca8145d722 h1:Qws2rZnQudC58cIagVucPQDLmMi3kAXgxscsgD0v6DU=
inet.af/netaddr v0.0.0-20210718074554-06ca8145d722/go.mod h1:z0nx+Dh+7N7CC8V5ayHtHGpZpxLQZZxkIaaz6HN65Ls=

View File

@@ -1,17 +1,9 @@
// Package alpine defines a configurator to interact with the Alpine operating system.
package alpine
import (
"os/user"
)
var _ Alpiner = (*Alpine)(nil)
type Alpiner interface {
UserCreater
VersionGetter
}
type Alpine struct {
alpineReleasePath string
passwdPath string

View File

@@ -12,10 +12,6 @@ var (
ErrUserAlreadyExists = errors.New("user already exists")
)
type UserCreater interface {
CreateUser(username string, uid int) (createdUsername string, err error)
}
// CreateUser creates a user in Alpine with the given UID.
func (a *Alpine) CreateUser(username string, uid int) (createdUsername string, err error) {
UIDStr := strconv.Itoa(uid)

View File

@@ -7,10 +7,6 @@ import (
"strings"
)
type VersionGetter interface {
Version(ctx context.Context) (version string, err error)
}
func (a *Alpine) Version(ctx context.Context) (version string, err error) {
file, err := os.OpenFile(a.alpineReleasePath, os.O_RDONLY, 0)
if err != nil {

View File

@@ -1,16 +1,5 @@
// Package cli defines an interface CLI to run command line operations.
package cli
var _ CLIer = (*CLI)(nil)
type CLIer interface {
ClientKeyFormatter
HealthChecker
OpenvpnConfigMaker
Updater
ServersFormatter
}
type CLI struct {
repoServersPath string
}

View File

@@ -7,16 +7,12 @@ import (
"os"
"strings"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/configuration/sources/files"
)
type ClientKeyFormatter interface {
ClientKey(args []string) error
}
func (c *CLI) ClientKey(args []string) error {
flagSet := flag.NewFlagSet("clientkey", flag.ExitOnError)
filepath := flagSet.String("path", constants.ClientKey, "file path to the client.key file")
filepath := flagSet.String("path", files.OpenVPNClientKeyPath, "file path to the client.key file")
if err := flagSet.Parse(args); err != nil {
return err
}

View File

@@ -6,51 +6,44 @@ import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/storage"
"golang.org/x/text/cases"
"golang.org/x/text/language"
)
type ServersFormatter interface {
FormatServers(args []string) error
}
var (
ErrFormatNotRecognized = errors.New("format is not recognized")
ErrProviderUnspecified = errors.New("VPN provider to format was not specified")
ErrOpenOutputFile = errors.New("cannot open output file")
ErrWriteOutput = errors.New("cannot write to output file")
ErrCloseOutputFile = errors.New("cannot close output file")
ErrFormatNotRecognized = errors.New("format is not recognized")
ErrProviderUnspecified = errors.New("VPN provider to format was not specified")
ErrMultipleProvidersToFormat = errors.New("more than one VPN provider to format were specified")
)
func addProviderFlag(flagSet *flag.FlagSet, providerToFormat map[string]*bool,
provider string, titleCaser cases.Caser) {
boolPtr, ok := providerToFormat[provider]
if !ok {
panic(fmt.Sprintf("unknown provider in format map: %s", provider))
}
flagSet.BoolVar(boolPtr, provider, false, "Format "+titleCaser.String(provider)+" servers")
}
func (c *CLI) FormatServers(args []string) error {
var format, output string
var cyberghost, expressvpn, fastestvpn, hideMyAss, ipvanish, ivpn, mullvad,
nordvpn, perfectPrivacy, pia, privado, privatevpn, protonvpn, purevpn, surfshark,
torguard, vpnUnlimited, vyprvpn, wevpn, windscribe bool
allProviders := providers.All()
providersToFormat := make(map[string]*bool, len(allProviders))
for _, provider := range allProviders {
providersToFormat[provider] = new(bool)
}
flagSet := flag.NewFlagSet("markdown", flag.ExitOnError)
flagSet.StringVar(&format, "format", "markdown", "Format to use which can be: 'markdown'")
flagSet.StringVar(&output, "output", "/dev/stdout", "Output file to write the formatted data to")
flagSet.BoolVar(&cyberghost, "cyberghost", false, "Format Cyberghost servers")
flagSet.BoolVar(&expressvpn, "expressvpn", false, "Format ExpressVPN servers")
flagSet.BoolVar(&fastestvpn, "fastestvpn", false, "Format FastestVPN servers")
flagSet.BoolVar(&hideMyAss, "hidemyass", false, "Format HideMyAss servers")
flagSet.BoolVar(&ipvanish, "ipvanish", false, "Format IpVanish servers")
flagSet.BoolVar(&ivpn, "ivpn", false, "Format IVPN servers")
flagSet.BoolVar(&mullvad, "mullvad", false, "Format Mullvad servers")
flagSet.BoolVar(&nordvpn, "nordvpn", false, "Format Nordvpn servers")
flagSet.BoolVar(&perfectPrivacy, "perfectprivacy", false, "Format Perfect Privacy servers")
flagSet.BoolVar(&pia, "pia", false, "Format Private Internet Access servers")
flagSet.BoolVar(&privado, "privado", false, "Format Privado servers")
flagSet.BoolVar(&privatevpn, "privatevpn", false, "Format Private VPN servers")
flagSet.BoolVar(&protonvpn, "protonvpn", false, "Format Protonvpn servers")
flagSet.BoolVar(&purevpn, "purevpn", false, "Format Purevpn servers")
flagSet.BoolVar(&surfshark, "surfshark", false, "Format Surfshark servers")
flagSet.BoolVar(&torguard, "torguard", false, "Format Torguard servers")
flagSet.BoolVar(&vpnUnlimited, "vpnunlimited", false, "Format VPN Unlimited servers")
flagSet.BoolVar(&vyprvpn, "vyprvpn", false, "Format Vyprvpn servers")
flagSet.BoolVar(&wevpn, "wevpn", false, "Format WeVPN servers")
flagSet.BoolVar(&windscribe, "windscribe", false, "Format Windscribe servers")
titleCaser := cases.Title(language.English)
for _, provider := range allProviders {
addProviderFlag(flagSet, providersToFormat, provider, titleCaser)
}
if err := flagSet.Parse(args); err != nil {
return err
}
@@ -59,74 +52,47 @@ func (c *CLI) FormatServers(args []string) error {
return fmt.Errorf("%w: %s", ErrFormatNotRecognized, format)
}
// Verify only one provider is set to be formatted.
var providers []string
for provider, formatPtr := range providersToFormat {
if *formatPtr {
providers = append(providers, provider)
}
}
switch len(providers) {
case 0:
return ErrProviderUnspecified
case 1:
default:
return fmt.Errorf("%w: %d specified: %s",
ErrMultipleProvidersToFormat, len(providers),
strings.Join(providers, ", "))
}
providerToFormat := providers[0]
logger := newNoopLogger()
storage, err := storage.New(logger, constants.ServersData)
if err != nil {
return fmt.Errorf("%w: %s", ErrNewStorage, err)
return fmt.Errorf("cannot create servers storage: %w", err)
}
currentServers := storage.GetServers()
var formatted string
switch {
case cyberghost:
formatted = currentServers.Cyberghost.ToMarkdown()
case expressvpn:
formatted = currentServers.Expressvpn.ToMarkdown()
case fastestvpn:
formatted = currentServers.Fastestvpn.ToMarkdown()
case hideMyAss:
formatted = currentServers.HideMyAss.ToMarkdown()
case ipvanish:
formatted = currentServers.Ipvanish.ToMarkdown()
case ivpn:
formatted = currentServers.Ivpn.ToMarkdown()
case mullvad:
formatted = currentServers.Mullvad.ToMarkdown()
case nordvpn:
formatted = currentServers.Nordvpn.ToMarkdown()
case perfectPrivacy:
formatted = currentServers.Perfectprivacy.ToMarkdown()
case pia:
formatted = currentServers.Pia.ToMarkdown()
case privado:
formatted = currentServers.Privado.ToMarkdown()
case privatevpn:
formatted = currentServers.Privatevpn.ToMarkdown()
case protonvpn:
formatted = currentServers.Protonvpn.ToMarkdown()
case purevpn:
formatted = currentServers.Purevpn.ToMarkdown()
case surfshark:
formatted = currentServers.Surfshark.ToMarkdown()
case torguard:
formatted = currentServers.Torguard.ToMarkdown()
case vpnUnlimited:
formatted = currentServers.VPNUnlimited.ToMarkdown()
case vyprvpn:
formatted = currentServers.Vyprvpn.ToMarkdown()
case wevpn:
formatted = currentServers.Wevpn.ToMarkdown()
case windscribe:
formatted = currentServers.Windscribe.ToMarkdown()
default:
return ErrProviderUnspecified
}
formatted := storage.FormatToMarkdown(providerToFormat)
output = filepath.Clean(output)
file, err := os.OpenFile(output, os.O_TRUNC|os.O_WRONLY|os.O_CREATE, 0644)
if err != nil {
return fmt.Errorf("%w: %s", ErrOpenOutputFile, err)
return fmt.Errorf("cannot open output file: %w", err)
}
_, err = fmt.Fprint(file, formatted)
if err != nil {
_ = file.Close()
return fmt.Errorf("%w: %s", ErrWriteOutput, err)
return fmt.Errorf("cannot write to output file: %w", err)
}
err = file.Close()
if err != nil {
return fmt.Errorf("%w: %s", ErrCloseOutputFile, err)
return fmt.Errorf("cannot close output file: %w", err)
}
return nil

View File

@@ -10,10 +10,6 @@ import (
"github.com/qdm12/gluetun/internal/healthcheck"
)
type HealthChecker interface {
HealthCheck(ctx context.Context, source sources.Source, warner Warner) error
}
func (c *CLI) HealthCheck(ctx context.Context, source sources.Source, warner Warner) error {
// Extract the health server port from the configuration.
config, err := source.ReadHealth()

View File

@@ -1,50 +1,73 @@
package cli
import (
"context"
"fmt"
"net"
"net/http"
"strings"
"time"
"github.com/qdm12/gluetun/internal/configuration/sources"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/openvpn/extract"
"github.com/qdm12/gluetun/internal/provider"
"github.com/qdm12/gluetun/internal/publicip/ipinfo"
"github.com/qdm12/gluetun/internal/storage"
"github.com/qdm12/gluetun/internal/updater/resolver"
)
type OpenvpnConfigMaker interface {
OpenvpnConfig(logger OpenvpnConfigLogger, source sources.Source) error
}
type OpenvpnConfigLogger interface {
Info(s string)
Warn(s string)
}
type Unzipper interface {
FetchAndExtract(ctx context.Context, url string) (
contents map[string][]byte, err error)
}
type ParallelResolver interface {
Resolve(ctx context.Context, settings resolver.ParallelSettings) (
hostToIPs map[string][]net.IP, warnings []string, err error)
}
type IPFetcher interface {
FetchMultiInfo(ctx context.Context, ips []net.IP) (data []ipinfo.Response, err error)
}
func (c *CLI) OpenvpnConfig(logger OpenvpnConfigLogger, source sources.Source) error {
storage, err := storage.New(logger, constants.ServersData)
if err != nil {
return err
}
allServers := storage.GetServers()
allSettings, err := source.Read()
if err != nil {
return err
}
if err = allSettings.Validate(allServers); err != nil {
if err = allSettings.Validate(storage); err != nil {
return err
}
providerConf := provider.New(*allSettings.VPN.Provider.Name, allServers, time.Now)
// Unused by this CLI command
unzipper := (Unzipper)(nil)
client := (*http.Client)(nil)
warner := (Warner)(nil)
parallelResolver := (ParallelResolver)(nil)
ipFetcher := (IPFetcher)(nil)
openvpnFileExtractor := extract.New()
providers := provider.NewProviders(storage, time.Now, warner, client,
unzipper, parallelResolver, ipFetcher, openvpnFileExtractor)
providerConf := providers.Get(*allSettings.VPN.Provider.Name)
connection, err := providerConf.GetConnection(allSettings.VPN.Provider.ServerSelection)
if err != nil {
return err
}
lines, err := providerConf.BuildConf(connection, allSettings.VPN.OpenVPN)
if err != nil {
return err
}
lines := providerConf.OpenVPNConfig(connection, allSettings.VPN.OpenVPN)
fmt.Println(strings.Join(lines, "\n"))
return nil

View File

@@ -2,53 +2,48 @@ package cli
import (
"context"
"encoding/json"
"errors"
"flag"
"fmt"
"net"
"net/http"
"os"
"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/constants/providers"
"github.com/qdm12/gluetun/internal/openvpn/extract"
"github.com/qdm12/gluetun/internal/provider"
"github.com/qdm12/gluetun/internal/publicip/ipinfo"
"github.com/qdm12/gluetun/internal/storage"
"github.com/qdm12/gluetun/internal/updater"
"github.com/qdm12/gluetun/internal/updater/resolver"
"github.com/qdm12/gluetun/internal/updater/unzip"
)
var (
ErrModeUnspecified = errors.New("at least one of -enduser or -maintainer must be specified")
ErrDNSAddress = errors.New("DNS address is not valid")
ErrNoProviderSpecified = errors.New("no provider was specified")
ErrNewStorage = errors.New("cannot create storage")
ErrUpdateServerInformation = errors.New("cannot update server information")
ErrWriteToFile = errors.New("cannot write updated information to file")
ErrModeUnspecified = errors.New("at least one of -enduser or -maintainer must be specified")
ErrNoProviderSpecified = errors.New("no provider was specified")
)
type Updater interface {
Update(ctx context.Context, args []string, logger UpdaterLogger) error
}
type UpdaterLogger interface {
Info(s string)
Warn(s string)
Error(s string)
}
func boolPtr(b bool) *bool { return &b }
func (c *CLI) Update(ctx context.Context, args []string, logger UpdaterLogger) error {
options := settings.Updater{CLI: boolPtr(true)}
options := settings.Updater{}
var endUserMode, maintainerMode, updateAll bool
var dnsAddress, csvProviders string
var csvProviders string
flagSet := flag.NewFlagSet("update", flag.ExitOnError)
flagSet.BoolVar(&endUserMode, "enduser", false, "Write results to /gluetun/servers.json (for end users)")
flagSet.BoolVar(&maintainerMode, "maintainer", false,
"Write results to ./internal/storage/servers.json to modify the program (for maintainers)")
flagSet.StringVar(&dnsAddress, "dns", "8.8.8.8", "DNS resolver address to use")
flagSet.StringVar(&options.DNSAddress, "dns", "8.8.8.8", "DNS resolver address to use")
const defaultMinRatio = 0.8
flagSet.Float64Var(&options.MinRatio, "minratio", defaultMinRatio,
"Minimum ratio of servers to find for the update to succeed")
flagSet.BoolVar(&updateAll, "all", false, "Update servers for all VPN providers")
flagSet.StringVar(&csvProviders, "providers", "", "CSV string of VPN providers to update server data for")
if err := flagSet.Parse(args); err != nil {
@@ -59,18 +54,8 @@ func (c *CLI) Update(ctx context.Context, args []string, logger UpdaterLogger) e
return ErrModeUnspecified
}
options.DNSAddress = net.ParseIP(dnsAddress)
if options.DNSAddress == nil {
return fmt.Errorf("%w: %s", ErrDNSAddress, dnsAddress)
}
if updateAll {
for _, provider := range constants.AllProviders() {
if provider == constants.Custom {
continue
}
options.Providers = append(options.Providers, provider)
}
options.Providers = providers.All()
} else {
if csvProviders == "" {
return ErrNoProviderSpecified
@@ -78,55 +63,40 @@ func (c *CLI) Update(ctx context.Context, args []string, logger UpdaterLogger) e
options.Providers = strings.Split(csvProviders, ",")
}
options.SetDefaults()
options.SetDefaults(options.Providers[0])
err := options.Validate()
if err != nil {
return fmt.Errorf("options validation failed: %w", err)
}
const clientTimeout = 10 * time.Second
httpClient := &http.Client{Timeout: clientTimeout}
storage, err := storage.New(logger, constants.ServersData)
if err != nil {
return fmt.Errorf("%w: %s", ErrNewStorage, err)
return fmt.Errorf("cannot create servers storage: %w", err)
}
currentServers := storage.GetServers()
updater := updater.New(options, httpClient, currentServers, logger)
allServers, err := updater.UpdateServers(ctx)
const clientTimeout = 10 * time.Second
httpClient := &http.Client{Timeout: clientTimeout}
unzipper := unzip.New(httpClient)
parallelResolver := resolver.NewParallelResolver(options.DNSAddress)
ipFetcher := ipinfo.New(httpClient)
openvpnFileExtractor := extract.New()
providers := provider.NewProviders(storage, time.Now, logger, httpClient,
unzipper, parallelResolver, ipFetcher, openvpnFileExtractor)
updater := updater.New(httpClient, storage, providers, logger)
err = updater.UpdateServers(ctx, options.Providers, options.MinRatio)
if err != nil {
return fmt.Errorf("%w: %s", ErrUpdateServerInformation, err)
}
if endUserMode {
if err := storage.FlushToFile(allServers); err != nil {
return fmt.Errorf("%w: %s", ErrWriteToFile, err)
}
return fmt.Errorf("cannot update server information: %w", err)
}
if maintainerMode {
if err := writeToEmbeddedJSON(c.repoServersPath, allServers); err != nil {
return fmt.Errorf("%w: %s", ErrWriteToFile, err)
err := storage.FlushToFile(c.repoServersPath)
if err != nil {
return fmt.Errorf("cannot write servers data to embedded JSON file: %w", err)
}
}
return nil
}
func writeToEmbeddedJSON(repoServersPath string,
allServers models.AllServers) error {
const perms = 0600
f, err := os.OpenFile(repoServersPath,
os.O_TRUNC|os.O_WRONLY|os.O_CREATE, perms)
if err != nil {
return err
}
defer f.Close()
encoder := json.NewEncoder(f)
encoder.SetIndent("", " ")
return encoder.Encode(allServers)
}

View File

@@ -65,7 +65,7 @@ func (d *DoT) copy() (copied DoT) {
// unset field of the receiver settings object.
func (d *DoT) mergeWith(other DoT) {
d.Enabled = helpers.MergeWithBool(d.Enabled, other.Enabled)
d.UpdatePeriod = helpers.MergeWithDuration(d.UpdatePeriod, other.UpdatePeriod)
d.UpdatePeriod = helpers.MergeWithDurationPtr(d.UpdatePeriod, other.UpdatePeriod)
d.Unbound.mergeWith(other.Unbound)
d.Blacklist.mergeWith(other.Blacklist)
}
@@ -75,7 +75,7 @@ func (d *DoT) mergeWith(other DoT) {
// settings.
func (d *DoT) overrideWith(other DoT) {
d.Enabled = helpers.OverrideWithBool(d.Enabled, other.Enabled)
d.UpdatePeriod = helpers.OverrideWithDuration(d.UpdatePeriod, other.UpdatePeriod)
d.UpdatePeriod = helpers.OverrideWithDurationPtr(d.UpdatePeriod, other.UpdatePeriod)
d.Unbound.overrideWith(other.Unbound)
d.Blacklist.overrideWith(other.Blacklist)
}
@@ -83,7 +83,7 @@ func (d *DoT) overrideWith(other DoT) {
func (d *DoT) setDefaults() {
d.Enabled = helpers.DefaultBool(d.Enabled, true)
const defaultUpdatePeriod = 24 * time.Hour
d.UpdatePeriod = helpers.DefaultDuration(d.UpdatePeriod, defaultUpdatePeriod)
d.UpdatePeriod = helpers.DefaultDurationPtr(d.UpdatePeriod, defaultUpdatePeriod)
d.Unbound.setDefaults()
d.Blacklist.setDefaults()
}

View File

@@ -6,18 +6,18 @@ var (
ErrCityNotValid = errors.New("the city specified is not valid")
ErrControlServerPrivilegedPort = errors.New("cannot use privileged port without running as root")
ErrCountryNotValid = errors.New("the country specified is not valid")
ErrFilepathMissing = errors.New("filepath is missing")
ErrFirewallZeroPort = errors.New("cannot have a zero port to block")
ErrHostnameNotValid = errors.New("the hostname specified is not valid")
ErrISPNotValid = errors.New("the ISP specified is not valid")
ErrMinRatioNotValid = errors.New("minimum ratio is not valid")
ErrMissingValue = errors.New("missing value")
ErrNameNotValid = errors.New("the server name specified is not valid")
ErrOpenVPNClientCertMissing = errors.New("client certificate is missing")
ErrOpenVPNClientCertNotValid = errors.New("client certificate is not valid")
ErrOpenVPNClientKeyMissing = errors.New("client key is missing")
ErrOpenVPNClientKeyNotValid = errors.New("client key is not valid")
ErrOpenVPNConfigFile = errors.New("custom configuration file error")
ErrOpenVPNCustomPortNotAllowed = errors.New("custom endpoint port is not allowed")
ErrOpenVPNEncryptionPresetNotValid = errors.New("PIA encryption preset is not valid")
ErrOpenVPNInterfaceNotValid = errors.New("interface name is not valid")
ErrOpenVPNKeyPassphraseIsEmpty = errors.New("key passphrase is empty")
ErrOpenVPNMSSFixIsTooHigh = errors.New("mssfix option value is too high")
ErrOpenVPNPasswordIsEmpty = errors.New("password is empty")
ErrOpenVPNTCPNotSupported = errors.New("TCP protocol is not supported")
@@ -25,14 +25,13 @@ var (
ErrOpenVPNVerbosityIsOutOfBounds = errors.New("verbosity value is out of bounds")
ErrOpenVPNVersionIsNotValid = errors.New("version is not valid")
ErrPortForwardingEnabled = errors.New("port forwarding cannot be enabled")
ErrPortForwardingFilepathNotValid = errors.New("port forwarding filepath given is not valid")
ErrPublicIPFilepathNotValid = errors.New("public IP address file path is not valid")
ErrPublicIPPeriodTooShort = errors.New("public IP address check period is too short")
ErrRegionNotValid = errors.New("the region specified is not valid")
ErrServerAddressNotValid = errors.New("server listening address is not valid")
ErrSystemPGIDNotValid = errors.New("process group id is not valid")
ErrSystemPUIDNotValid = errors.New("process user id is not valid")
ErrSystemTimezoneNotValid = errors.New("timezone is not valid")
ErrUpdaterPeriodTooSmall = errors.New("VPN server data updater period is too small")
ErrVPNProviderNameNotValid = errors.New("VPN provider name is not valid")
ErrVPNTypeNotValid = errors.New("VPN type is not valid")
ErrWireguardEndpointIPNotSet = errors.New("endpoint IP is not set")
@@ -41,11 +40,7 @@ var (
ErrWireguardInterfaceAddressNotSet = errors.New("interface address is not set")
ErrWireguardInterfaceNotValid = errors.New("interface name is not valid")
ErrWireguardPreSharedKeyNotSet = errors.New("pre-shared key is not set")
ErrWireguardPreSharedKeyNotValid = errors.New("pre-shared key is not valid")
ErrWireguardPrivateKeyNotSet = errors.New("private key is not set")
ErrWireguardPrivateKeyNotValid = errors.New("private key is not valid")
ErrWireguardPublicKeyNotSet = errors.New("public key is not set")
ErrWireguardPublicKeyNotValid = errors.New("public key is not valid")
ErrUpdaterPeriodTooSmall = errors.New("VPN server data updater period is too small")
)

View File

@@ -3,6 +3,7 @@ package settings
import (
"fmt"
"os"
"time"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gotree"
@@ -15,10 +16,16 @@ type Health struct {
// for the health check server.
// It cannot be the empty string in the internal state.
ServerAddress string
// AddressToPing is the IP address or domain name to
// ping periodically for the health check.
// ReadHeaderTimeout is the HTTP server header read timeout
// duration of the HTTP server. It defaults to 100 milliseconds.
ReadHeaderTimeout time.Duration
// ReadTimeout is the HTTP read timeout duration of the
// HTTP server. It defaults to 500 milliseconds.
ReadTimeout time.Duration
// TargetAddress is the address (host or host:port)
// to TCP dial to periodically for the health check.
// It cannot be the empty string in the internal state.
AddressToPing string
TargetAddress string
VPN HealthyWait
}
@@ -27,13 +34,12 @@ func (h Health) Validate() (err error) {
_, err = address.Validate(h.ServerAddress,
address.OptionListening(uid))
if err != nil {
return fmt.Errorf("%w: %s",
ErrServerAddressNotValid, err)
return fmt.Errorf("server listening address is not valid: %w", err)
}
err = h.VPN.validate()
if err != nil {
return fmt.Errorf("health VPN settings validation failed: %w", err)
return fmt.Errorf("health VPN settings: %w", err)
}
return nil
@@ -41,9 +47,11 @@ func (h Health) Validate() (err error) {
func (h *Health) copy() (copied Health) {
return Health{
ServerAddress: h.ServerAddress,
AddressToPing: h.AddressToPing,
VPN: h.VPN.copy(),
ServerAddress: h.ServerAddress,
ReadHeaderTimeout: h.ReadHeaderTimeout,
ReadTimeout: h.ReadTimeout,
TargetAddress: h.TargetAddress,
VPN: h.VPN.copy(),
}
}
@@ -51,7 +59,9 @@ func (h *Health) copy() (copied Health) {
// unset field of the receiver settings object.
func (h *Health) MergeWith(other Health) {
h.ServerAddress = helpers.MergeWithString(h.ServerAddress, other.ServerAddress)
h.AddressToPing = helpers.MergeWithString(h.AddressToPing, other.AddressToPing)
h.ReadHeaderTimeout = helpers.MergeWithDuration(h.ReadHeaderTimeout, other.ReadHeaderTimeout)
h.ReadTimeout = helpers.MergeWithDuration(h.ReadTimeout, other.ReadTimeout)
h.TargetAddress = helpers.MergeWithString(h.TargetAddress, other.TargetAddress)
h.VPN.mergeWith(other.VPN)
}
@@ -60,13 +70,19 @@ func (h *Health) MergeWith(other Health) {
// settings.
func (h *Health) OverrideWith(other Health) {
h.ServerAddress = helpers.OverrideWithString(h.ServerAddress, other.ServerAddress)
h.AddressToPing = helpers.OverrideWithString(h.AddressToPing, other.AddressToPing)
h.ReadHeaderTimeout = helpers.OverrideWithDuration(h.ReadHeaderTimeout, other.ReadHeaderTimeout)
h.ReadTimeout = helpers.OverrideWithDuration(h.ReadTimeout, other.ReadTimeout)
h.TargetAddress = helpers.OverrideWithString(h.TargetAddress, other.TargetAddress)
h.VPN.overrideWith(other.VPN)
}
func (h *Health) SetDefaults() {
h.ServerAddress = helpers.DefaultString(h.ServerAddress, "127.0.0.1:9999")
h.AddressToPing = helpers.DefaultString(h.AddressToPing, "github.com")
const defaultReadHeaderTimeout = 100 * time.Millisecond
h.ReadHeaderTimeout = helpers.DefaultDuration(h.ReadHeaderTimeout, defaultReadHeaderTimeout)
const defaultReadTimeout = 500 * time.Millisecond
h.ReadTimeout = helpers.DefaultDuration(h.ReadTimeout, defaultReadTimeout)
h.TargetAddress = helpers.DefaultString(h.TargetAddress, "cloudflare.com:443")
h.VPN.setDefaults()
}
@@ -77,7 +93,9 @@ func (h Health) String() string {
func (h Health) toLinesNode() (node *gotree.Node) {
node = gotree.New("Health settings:")
node.Appendf("Server listening address: %s", h.ServerAddress)
node.Appendf("Address to ping: %s", h.AddressToPing)
node.Appendf("Target address: %s", h.TargetAddress)
node.Appendf("Read header timeout: %s", h.ReadHeaderTimeout)
node.Appendf("Read timeout: %s", h.ReadTimeout)
node.AppendNode(h.VPN.toLinesNode("VPN"))
return node
}

View File

@@ -35,23 +35,23 @@ 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 = helpers.MergeWithDuration(h.Initial, other.Initial)
h.Addition = helpers.MergeWithDuration(h.Addition, other.Addition)
h.Initial = helpers.MergeWithDurationPtr(h.Initial, other.Initial)
h.Addition = helpers.MergeWithDurationPtr(h.Addition, other.Addition)
}
// overrideWith overrides fields of the receiver
// settings object with any field set in the other
// settings.
func (h *HealthyWait) overrideWith(other HealthyWait) {
h.Initial = helpers.OverrideWithDuration(h.Initial, other.Initial)
h.Addition = helpers.OverrideWithDuration(h.Addition, other.Addition)
h.Initial = helpers.OverrideWithDurationPtr(h.Initial, other.Initial)
h.Addition = helpers.OverrideWithDurationPtr(h.Addition, other.Addition)
}
func (h *HealthyWait) setDefaults() {
const initialDurationDefault = 6 * time.Second
const additionDurationDefault = 5 * time.Second
h.Initial = helpers.DefaultDuration(h.Initial, initialDurationDefault)
h.Addition = helpers.DefaultDuration(h.Addition, additionDurationDefault)
h.Initial = helpers.DefaultDurationPtr(h.Initial, initialDurationDefault)
h.Addition = helpers.DefaultDurationPtr(h.Addition, additionDurationDefault)
}
func (h HealthyWait) String() string {

View File

@@ -15,9 +15,16 @@ func IsOneOf(value string, choices ...string) (ok bool) {
return false
}
var ErrValueNotOneOf = errors.New("value is not one of the possible choices")
var (
ErrNoChoice = errors.New("one or more values is set but there is no possible value available")
ErrValueNotOneOf = errors.New("value is not one of the possible choices")
)
func AreAllOneOf(values, choices []string) (err error) {
if len(values) > 0 && len(choices) == 0 {
return ErrNoChoice
}
set := make(map[string]struct{}, len(choices))
for _, choice := range choices {
choice = strings.ToLower(choice)

View File

@@ -4,7 +4,7 @@ import (
"net"
"time"
"github.com/qdm12/golibs/logging"
"github.com/qdm12/log"
"inet.af/netaddr"
)
@@ -44,6 +44,15 @@ func CopyUint16Ptr(original *uint16) (copied *uint16) {
return copied
}
func CopyUint32Ptr(original *uint32) (copied *uint32) {
if original == nil {
return nil
}
copied = new(uint32)
*copied = *original
return copied
}
func CopyIntPtr(original *int) (copied *int) {
if original == nil {
return nil
@@ -62,11 +71,11 @@ func CopyDurationPtr(original *time.Duration) (copied *time.Duration) {
return copied
}
func CopyLogLevelPtr(original *logging.Level) (copied *logging.Level) {
func CopyLogLevelPtr(original *log.Level) (copied *log.Level) {
if original == nil {
return nil
}
copied = new(logging.Level)
copied = new(log.Level)
*copied = *original
return copied
}

View File

@@ -4,7 +4,7 @@ import (
"net"
"time"
"github.com/qdm12/golibs/logging"
"github.com/qdm12/log"
)
func DefaultInt(existing *int, defaultValue int) (
@@ -36,6 +36,15 @@ func DefaultUint16(existing *uint16, defaultValue uint16) (
*result = defaultValue
return result
}
func DefaultUint32(existing *uint32, defaultValue uint32) (
result *uint32) {
if existing != nil {
return existing
}
result = new(uint32)
*result = defaultValue
return result
}
func DefaultBool(existing *bool, defaultValue bool) (
result *bool) {
@@ -64,7 +73,15 @@ func DefaultStringPtr(existing *string, defaultValue string) (result *string) {
return result
}
func DefaultDuration(existing *time.Duration,
func DefaultDuration(existing time.Duration,
defaultValue time.Duration) (result time.Duration) {
if existing != 0 {
return existing
}
return defaultValue
}
func DefaultDurationPtr(existing *time.Duration,
defaultValue time.Duration) (result *time.Duration) {
if existing != nil {
return existing
@@ -74,12 +91,12 @@ func DefaultDuration(existing *time.Duration,
return result
}
func DefaultLogLevel(existing *logging.Level,
defaultValue logging.Level) (result *logging.Level) {
func DefaultLogLevel(existing *log.Level,
defaultValue log.Level) (result *log.Level) {
if existing != nil {
return existing
}
result = new(logging.Level)
result = new(log.Level)
*result = defaultValue
return result
}

View File

@@ -2,9 +2,10 @@ package helpers
import (
"net"
"net/http"
"time"
"github.com/qdm12/golibs/logging"
"github.com/qdm12/log"
"inet.af/netaddr"
)
@@ -26,6 +27,20 @@ func MergeWithString(existing, other string) (result string) {
return other
}
func MergeWithInt(existing, other int) (result int) {
if existing != 0 {
return existing
}
return other
}
func MergeWithFloat64(existing, other float64) (result float64) {
if existing != 0 {
return existing
}
return other
}
func MergeWithStringPtr(existing, other *string) (result *string) {
if existing != nil {
return existing
@@ -37,7 +52,7 @@ func MergeWithStringPtr(existing, other *string) (result *string) {
return result
}
func MergeWithInt(existing, other *int) (result *int) {
func MergeWithIntPtr(existing, other *int) (result *int) {
if existing != nil {
return existing
} else if other == nil {
@@ -70,6 +85,17 @@ func MergeWithUint16(existing, other *uint16) (result *uint16) {
return result
}
func MergeWithUint32(existing, other *uint32) (result *uint32) {
if existing != nil {
return existing
} else if other == nil {
return nil
}
result = new(uint32)
*result = *other
return result
}
func MergeWithIP(existing, other net.IP) (result net.IP) {
if existing != nil {
return existing
@@ -81,24 +107,38 @@ func MergeWithIP(existing, other net.IP) (result net.IP) {
return result
}
func MergeWithDuration(existing, other *time.Duration) (result *time.Duration) {
func MergeWithDuration(existing, other time.Duration) (result time.Duration) {
if existing != 0 {
return existing
}
return other
}
func MergeWithDurationPtr(existing, other *time.Duration) (result *time.Duration) {
if existing != nil {
return existing
}
return other
}
func MergeWithLogLevel(existing, other *logging.Level) (result *logging.Level) {
func MergeWithLogLevel(existing, other *log.Level) (result *log.Level) {
if existing != nil {
return existing
} else if other == nil {
return nil
}
result = new(logging.Level)
result = new(log.Level)
*result = *other
return result
}
func MergeWithHTTPHandler(existing, other http.Handler) (result http.Handler) {
if existing != nil {
return existing
}
return other
}
func MergeStringSlices(a, b []string) (result []string) {
if a == nil && b == nil {
return nil

View File

@@ -2,9 +2,10 @@ package helpers
import (
"net"
"net/http"
"time"
"github.com/qdm12/golibs/logging"
"github.com/qdm12/log"
"inet.af/netaddr"
)
@@ -24,6 +25,20 @@ func OverrideWithString(existing, other string) (result string) {
return other
}
func OverrideWithInt(existing, other int) (result int) {
if other == 0 {
return existing
}
return other
}
func OverrideWithFloat64(existing, other float64) (result float64) {
if other == 0 {
return existing
}
return other
}
func OverrideWithStringPtr(existing, other *string) (result *string) {
if other == nil {
return existing
@@ -33,7 +48,7 @@ func OverrideWithStringPtr(existing, other *string) (result *string) {
return result
}
func OverrideWithInt(existing, other *int) (result *int) {
func OverrideWithIntPtr(existing, other *int) (result *int) {
if other == nil {
return existing
}
@@ -60,6 +75,15 @@ func OverrideWithUint16(existing, other *uint16) (result *uint16) {
return result
}
func OverrideWithUint32(existing, other *uint32) (result *uint32) {
if other == nil {
return existing
}
result = new(uint32)
*result = *other
return result
}
func OverrideWithIP(existing, other net.IP) (result net.IP) {
if other == nil {
return existing
@@ -69,7 +93,16 @@ func OverrideWithIP(existing, other net.IP) (result net.IP) {
return result
}
func OverrideWithDuration(existing, other *time.Duration) (result *time.Duration) {
func OverrideWithDuration(existing, other time.Duration) (
result time.Duration) {
if other == 0 {
return existing
}
return other
}
func OverrideWithDurationPtr(existing, other *time.Duration) (
result *time.Duration) {
if other == nil {
return existing
}
@@ -78,15 +111,22 @@ func OverrideWithDuration(existing, other *time.Duration) (result *time.Duration
return result
}
func OverrideWithLogLevel(existing, other *logging.Level) (result *logging.Level) {
func OverrideWithLogLevel(existing, other *log.Level) (result *log.Level) {
if other == nil {
return existing
}
result = new(logging.Level)
result = new(log.Level)
*result = *other
return result
}
func OverrideWithHTTPHandler(existing, other http.Handler) (result http.Handler) {
if other != nil {
return other
}
return existing
}
func OverrideWithStringSlice(existing, other []string) (result []string) {
if other == nil {
return existing

View File

@@ -3,6 +3,7 @@ package settings
import (
"fmt"
"os"
"time"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gotree"
@@ -33,6 +34,12 @@ type HTTPProxy struct {
// each request/response. It cannot be nil in the
// internal state.
Log *bool
// ReadHeaderTimeout is the HTTP header read timeout duration
// of the HTTP server. It defaults to 1 second if left unset.
ReadHeaderTimeout time.Duration
// ReadTimeout is the HTTP read timeout duration
// of the HTTP server. It defaults to 3 seconds if left unset.
ReadTimeout time.Duration
}
func (h HTTPProxy) validate() (err error) {
@@ -41,8 +48,7 @@ func (h HTTPProxy) validate() (err error) {
uid := os.Getuid()
_, err = address.Validate(h.ListeningAddress, address.OptionListening(uid))
if err != nil {
return fmt.Errorf("%w: %s",
ErrServerAddressNotValid, h.ListeningAddress)
return fmt.Errorf("%w: %s", ErrServerAddressNotValid, h.ListeningAddress)
}
return nil
@@ -50,12 +56,14 @@ func (h HTTPProxy) validate() (err error) {
func (h *HTTPProxy) copy() (copied HTTPProxy) {
return HTTPProxy{
User: helpers.CopyStringPtr(h.User),
Password: helpers.CopyStringPtr(h.Password),
ListeningAddress: h.ListeningAddress,
Enabled: helpers.CopyBoolPtr(h.Enabled),
Stealth: helpers.CopyBoolPtr(h.Stealth),
Log: helpers.CopyBoolPtr(h.Log),
User: helpers.CopyStringPtr(h.User),
Password: helpers.CopyStringPtr(h.Password),
ListeningAddress: h.ListeningAddress,
Enabled: helpers.CopyBoolPtr(h.Enabled),
Stealth: helpers.CopyBoolPtr(h.Stealth),
Log: helpers.CopyBoolPtr(h.Log),
ReadHeaderTimeout: h.ReadHeaderTimeout,
ReadTimeout: h.ReadTimeout,
}
}
@@ -68,6 +76,8 @@ func (h *HTTPProxy) mergeWith(other HTTPProxy) {
h.Enabled = helpers.MergeWithBool(h.Enabled, other.Enabled)
h.Stealth = helpers.MergeWithBool(h.Stealth, other.Stealth)
h.Log = helpers.MergeWithBool(h.Log, other.Log)
h.ReadHeaderTimeout = helpers.MergeWithDuration(h.ReadHeaderTimeout, other.ReadHeaderTimeout)
h.ReadTimeout = helpers.MergeWithDuration(h.ReadTimeout, other.ReadTimeout)
}
// overrideWith overrides fields of the receiver
@@ -80,6 +90,8 @@ func (h *HTTPProxy) overrideWith(other HTTPProxy) {
h.Enabled = helpers.OverrideWithBool(h.Enabled, other.Enabled)
h.Stealth = helpers.OverrideWithBool(h.Stealth, other.Stealth)
h.Log = helpers.OverrideWithBool(h.Log, other.Log)
h.ReadHeaderTimeout = helpers.OverrideWithDuration(h.ReadHeaderTimeout, other.ReadHeaderTimeout)
h.ReadTimeout = helpers.OverrideWithDuration(h.ReadTimeout, other.ReadTimeout)
}
func (h *HTTPProxy) setDefaults() {
@@ -89,6 +101,10 @@ func (h *HTTPProxy) setDefaults() {
h.Enabled = helpers.DefaultBool(h.Enabled, false)
h.Stealth = helpers.DefaultBool(h.Stealth, false)
h.Log = helpers.DefaultBool(h.Log, false)
const defaultReadHeaderTimeout = time.Second
h.ReadHeaderTimeout = helpers.DefaultDuration(h.ReadHeaderTimeout, defaultReadHeaderTimeout)
const defaultReadTimeout = 3 * time.Second
h.ReadTimeout = helpers.DefaultDuration(h.ReadTimeout, defaultReadTimeout)
}
func (h HTTPProxy) String() string {
@@ -107,6 +123,8 @@ func (h HTTPProxy) toLinesNode() (node *gotree.Node) {
node.Appendf("Password: %s", helpers.ObfuscatePassword(*h.Password))
node.Appendf("Stealth mode: %s", helpers.BoolPtrToYesNo(h.Stealth))
node.Appendf("Log: %s", helpers.BoolPtrToYesNo(h.Log))
node.Appendf("Read header timeout: %s", h.ReadHeaderTimeout)
node.Appendf("Read timeout: %s", h.ReadTimeout)
return node
}

View File

@@ -2,15 +2,15 @@ package settings
import (
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/golibs/logging"
"github.com/qdm12/gotree"
"github.com/qdm12/log"
)
// 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 *logging.Level
Level *log.Level
}
func (l Log) validate() (err error) {
@@ -37,7 +37,7 @@ func (l *Log) overrideWith(other Log) {
}
func (l *Log) setDefaults() {
l.Level = helpers.DefaultLogLevel(l.Level, logging.LevelInfo)
l.Level = helpers.DefaultLogLevel(l.Level, log.LevelInfo)
}
func (l Log) String() string {

View File

@@ -1,12 +1,16 @@
package settings
import (
"encoding/base64"
"fmt"
"regexp"
"strings"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/openvpn/parse"
"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/gotree"
)
@@ -16,13 +20,15 @@ type OpenVPN struct {
// It can only be "2.4" or "2.5".
Version string
// User is the OpenVPN authentication username.
// It cannot be an empty string in the internal state
// if OpenVPN is used.
User string
// It cannot be nil in the internal state if OpenVPN is used.
// It is usually required but in some cases can be the empty string
// to indicate no user+password authentication is needed.
User *string
// Password is the OpenVPN authentication password.
// It cannot be an empty string in the internal state
// if OpenVPN is used.
Password string
// It cannot be nil in the internal state if OpenVPN is used.
// It is usually required but in some cases can be the empty string
// to indicate no user+password authentication is needed.
Password *string
// ConfFile is a custom OpenVPN configuration file path.
// It can be set to the empty string for it to be ignored.
// It cannot be nil in the internal state.
@@ -36,16 +42,25 @@ type OpenVPN struct {
// It cannot be nil in the internal state.
// It is ignored if it is set to the empty string.
Auth *string
// ClientCrt is the OpenVPN client certificate.
// This is notably used by Cyberghost.
// Cert is the OpenVPN certificate for the <cert> block.
// This is notably used by Cyberghost and VPN secure.
// It can be set to the empty string to be ignored.
// It cannot be nil in the internal state.
ClientCrt *string
// ClientKey is the OpenVPN client key.
Cert *string
// Key is the OpenVPN key.
// This is used by Cyberghost and VPN Unlimited.
// It can be set to the empty string to be ignored.
// It cannot be nil in the internal state.
ClientKey *string
Key *string
// EncryptedKey is the content of an encrypted
// key for OpenVPN. It is used by VPN secure.
// It defaults to the empty string meaning it is not
// to be used. KeyPassphrase must be set if this one is set.
EncryptedKey *string
// KeyPassphrase is the key passphrase to be used by OpenVPN
// to decrypt the EncryptedPrivateKey. It defaults to the
// empty string and must be set if EncryptedPrivateKey is set.
KeyPassphrase *string
// PIAEncPreset is the encryption preset for
// Private Internet Access. It can be set to an
// empty string for other providers.
@@ -61,15 +76,10 @@ type OpenVPN struct {
// Interface is the OpenVPN device interface name.
// It cannot be an empty string in the internal state.
Interface string
// Root is true if OpenVPN is to be run as root,
// and false otherwise. It cannot be nil in the
// internal state.
Root *bool
// ProcUser is the OpenVPN process OS username
// to use. It cannot be nil in the internal state.
// This is set and injected at runtime.
// TODO only use ProcUser and not Root field.
ProcUser string
// ProcessUser is the OpenVPN process OS username
// to use. It cannot be empty in the internal state.
// It defaults to 'root'.
ProcessUser string
// Verbosity is the OpenVPN verbosity level from 0 to 6.
// It cannot be nil in the internal state.
Verbosity *int
@@ -78,69 +88,54 @@ type OpenVPN struct {
Flags []string
}
var ivpnAccountID = regexp.MustCompile(`^(i|ivpn)\-[a-zA-Z0-9]{4}\-[a-zA-Z0-9]{4}\-[a-zA-Z0-9]{4}$`)
func (o OpenVPN) validate(vpnProvider string) (err error) {
// Validate version
validVersions := []string{constants.Openvpn24, constants.Openvpn25}
validVersions := []string{openvpn.Openvpn24, openvpn.Openvpn25}
if !helpers.IsOneOf(o.Version, validVersions...) {
return fmt.Errorf("%w: %q can only be one of %s",
ErrOpenVPNVersionIsNotValid, o.Version, strings.Join(validVersions, ", "))
}
isCustom := vpnProvider == constants.Custom
isCustom := vpnProvider == providers.Custom
isUserRequired := !isCustom && vpnProvider != providers.VPNSecure
if !isCustom && o.User == "" {
if isUserRequired && *o.User == "" {
return ErrOpenVPNUserIsEmpty
}
if !isCustom && o.Password == "" {
passwordRequired := isUserRequired &&
(vpnProvider != providers.Ivpn || !ivpnAccountID.MatchString(*o.User))
if passwordRequired && *o.Password == "" {
return ErrOpenVPNPasswordIsEmpty
}
// Validate ConfFile
if isCustom {
if *o.ConfFile == "" {
return fmt.Errorf("%w: no file path specified", ErrOpenVPNConfigFile)
}
err := helpers.FileExists(*o.ConfFile)
if err != nil {
return fmt.Errorf("%w: %s", ErrOpenVPNConfigFile, err)
}
err = validateOpenVPNConfigFilepath(isCustom, *o.ConfFile)
if err != nil {
return fmt.Errorf("custom configuration file: %w", err)
}
// Check client certificate
switch vpnProvider {
case
constants.Cyberghost,
constants.VPNUnlimited:
if *o.ClientCrt == "" {
return ErrOpenVPNClientCertMissing
}
}
if *o.ClientCrt != "" {
_, err = parse.ExtractCert([]byte(*o.ClientCrt))
if err != nil {
return fmt.Errorf("%w: %s", ErrOpenVPNClientCertNotValid, err)
}
err = validateOpenVPNClientCertificate(vpnProvider, *o.Cert)
if err != nil {
return fmt.Errorf("client certificate: %w", err)
}
// Check client key
switch vpnProvider {
case
constants.Cyberghost,
constants.VPNUnlimited,
constants.Wevpn:
if *o.ClientKey == "" {
return ErrOpenVPNClientKeyMissing
}
}
if *o.ClientKey != "" {
_, err = parse.ExtractPrivateKey([]byte(*o.ClientKey))
if err != nil {
return fmt.Errorf("%w: %s", ErrOpenVPNClientKeyNotValid, err)
}
err = validateOpenVPNClientKey(vpnProvider, *o.Key)
if err != nil {
return fmt.Errorf("client key: %w", err)
}
err = validateOpenVPNEncryptedKey(vpnProvider, *o.EncryptedKey)
if err != nil {
return fmt.Errorf("encrypted key: %w", err)
}
if *o.EncryptedKey != "" && *o.KeyPassphrase == "" {
return fmt.Errorf("%w", ErrOpenVPNKeyPassphraseIsEmpty)
}
// Validate MSSFix
const maxMSSFix = 10000
if *o.MSSFix > maxMSSFix {
return fmt.Errorf("%w: %d is over the maximum value of %d",
@@ -152,7 +147,6 @@ func (o OpenVPN) validate(vpnProvider string) (err error) {
ErrOpenVPNInterfaceNotValid, o.Interface, regexpInterfaceName)
}
// Validate Verbosity
if *o.Verbosity < 0 || *o.Verbosity > 6 {
return fmt.Errorf("%w: %d can only be between 0 and 5",
ErrOpenVPNVerbosityIsOutOfBounds, o.Verbosity)
@@ -161,24 +155,111 @@ func (o OpenVPN) validate(vpnProvider string) (err error) {
return nil
}
func validateOpenVPNConfigFilepath(isCustom bool,
confFile string) (err error) {
if !isCustom {
return nil
}
if confFile == "" {
return ErrFilepathMissing
}
err = helpers.FileExists(confFile)
if err != nil {
return err
}
extractor := extract.New()
_, _, err = extractor.Data(confFile)
if err != nil {
return fmt.Errorf("failed extracting information from custom configuration file: %w", err)
}
return nil
}
func validateOpenVPNClientCertificate(vpnProvider,
clientCert string) (err error) {
switch vpnProvider {
case
providers.Cyberghost,
providers.VPNSecure,
providers.VPNUnlimited:
if clientCert == "" {
return ErrMissingValue
}
}
if clientCert == "" {
return nil
}
_, err = base64.StdEncoding.DecodeString(clientCert)
if err != nil {
return err
}
return nil
}
func validateOpenVPNClientKey(vpnProvider, clientKey string) (err error) {
switch vpnProvider {
case
providers.Cyberghost,
providers.VPNUnlimited,
providers.Wevpn:
if clientKey == "" {
return ErrMissingValue
}
}
if clientKey == "" {
return nil
}
_, err = base64.StdEncoding.DecodeString(clientKey)
if err != nil {
return err
}
return nil
}
func validateOpenVPNEncryptedKey(vpnProvider,
encryptedPrivateKey string) (err error) {
if vpnProvider == providers.VPNSecure && encryptedPrivateKey == "" {
return ErrMissingValue
}
if encryptedPrivateKey == "" {
return nil
}
_, err = base64.StdEncoding.DecodeString(encryptedPrivateKey)
if err != nil {
return err
}
return nil
}
func (o *OpenVPN) copy() (copied OpenVPN) {
return OpenVPN{
Version: o.Version,
User: o.User,
Password: o.Password,
ConfFile: helpers.CopyStringPtr(o.ConfFile),
Ciphers: helpers.CopyStringSlice(o.Ciphers),
Auth: helpers.CopyStringPtr(o.Auth),
ClientCrt: helpers.CopyStringPtr(o.ClientCrt),
ClientKey: helpers.CopyStringPtr(o.ClientKey),
PIAEncPreset: helpers.CopyStringPtr(o.PIAEncPreset),
IPv6: helpers.CopyBoolPtr(o.IPv6),
MSSFix: helpers.CopyUint16Ptr(o.MSSFix),
Interface: o.Interface,
Root: helpers.CopyBoolPtr(o.Root),
ProcUser: o.ProcUser,
Verbosity: helpers.CopyIntPtr(o.Verbosity),
Flags: helpers.CopyStringSlice(o.Flags),
Version: o.Version,
User: helpers.CopyStringPtr(o.User),
Password: helpers.CopyStringPtr(o.Password),
ConfFile: helpers.CopyStringPtr(o.ConfFile),
Ciphers: helpers.CopyStringSlice(o.Ciphers),
Auth: helpers.CopyStringPtr(o.Auth),
Cert: helpers.CopyStringPtr(o.Cert),
Key: helpers.CopyStringPtr(o.Key),
EncryptedKey: helpers.CopyStringPtr(o.EncryptedKey),
KeyPassphrase: helpers.CopyStringPtr(o.KeyPassphrase),
PIAEncPreset: helpers.CopyStringPtr(o.PIAEncPreset),
IPv6: helpers.CopyBoolPtr(o.IPv6),
MSSFix: helpers.CopyUint16Ptr(o.MSSFix),
Interface: o.Interface,
ProcessUser: o.ProcessUser,
Verbosity: helpers.CopyIntPtr(o.Verbosity),
Flags: helpers.CopyStringSlice(o.Flags),
}
}
@@ -186,20 +267,21 @@ func (o *OpenVPN) copy() (copied OpenVPN) {
// unset field of the receiver settings object.
func (o *OpenVPN) mergeWith(other OpenVPN) {
o.Version = helpers.MergeWithString(o.Version, other.Version)
o.User = helpers.MergeWithString(o.User, other.User)
o.Password = helpers.MergeWithString(o.Password, other.Password)
o.User = helpers.MergeWithStringPtr(o.User, other.User)
o.Password = helpers.MergeWithStringPtr(o.Password, other.Password)
o.ConfFile = helpers.MergeWithStringPtr(o.ConfFile, other.ConfFile)
o.Ciphers = helpers.MergeStringSlices(o.Ciphers, other.Ciphers)
o.Auth = helpers.MergeWithStringPtr(o.Auth, other.Auth)
o.ClientCrt = helpers.MergeWithStringPtr(o.ClientCrt, other.ClientCrt)
o.ClientKey = helpers.MergeWithStringPtr(o.ClientKey, other.ClientKey)
o.Cert = helpers.MergeWithStringPtr(o.Cert, other.Cert)
o.Key = helpers.MergeWithStringPtr(o.Key, other.Key)
o.EncryptedKey = helpers.MergeWithStringPtr(o.EncryptedKey, other.EncryptedKey)
o.KeyPassphrase = helpers.MergeWithStringPtr(o.KeyPassphrase, other.KeyPassphrase)
o.PIAEncPreset = helpers.MergeWithStringPtr(o.PIAEncPreset, other.PIAEncPreset)
o.IPv6 = helpers.MergeWithBool(o.IPv6, other.IPv6)
o.MSSFix = helpers.MergeWithUint16(o.MSSFix, other.MSSFix)
o.Interface = helpers.MergeWithString(o.Interface, other.Interface)
o.Root = helpers.MergeWithBool(o.Root, other.Root)
o.ProcUser = helpers.MergeWithString(o.ProcUser, other.ProcUser)
o.Verbosity = helpers.MergeWithInt(o.Verbosity, other.Verbosity)
o.ProcessUser = helpers.MergeWithString(o.ProcessUser, other.ProcessUser)
o.Verbosity = helpers.MergeWithIntPtr(o.Verbosity, other.Verbosity)
o.Flags = helpers.MergeStringSlices(o.Flags, other.Flags)
}
@@ -208,45 +290,50 @@ func (o *OpenVPN) mergeWith(other OpenVPN) {
// settings.
func (o *OpenVPN) overrideWith(other OpenVPN) {
o.Version = helpers.OverrideWithString(o.Version, other.Version)
o.User = helpers.OverrideWithString(o.User, other.User)
o.Password = helpers.OverrideWithString(o.Password, other.Password)
o.User = helpers.OverrideWithStringPtr(o.User, other.User)
o.Password = helpers.OverrideWithStringPtr(o.Password, other.Password)
o.ConfFile = helpers.OverrideWithStringPtr(o.ConfFile, other.ConfFile)
o.Ciphers = helpers.OverrideWithStringSlice(o.Ciphers, other.Ciphers)
o.Auth = helpers.OverrideWithStringPtr(o.Auth, other.Auth)
o.ClientCrt = helpers.OverrideWithStringPtr(o.ClientCrt, other.ClientCrt)
o.ClientKey = helpers.OverrideWithStringPtr(o.ClientKey, other.ClientKey)
o.Cert = helpers.OverrideWithStringPtr(o.Cert, other.Cert)
o.Key = helpers.OverrideWithStringPtr(o.Key, other.Key)
o.EncryptedKey = helpers.OverrideWithStringPtr(o.EncryptedKey, other.EncryptedKey)
o.KeyPassphrase = helpers.OverrideWithStringPtr(o.KeyPassphrase, other.KeyPassphrase)
o.PIAEncPreset = helpers.OverrideWithStringPtr(o.PIAEncPreset, other.PIAEncPreset)
o.IPv6 = helpers.OverrideWithBool(o.IPv6, other.IPv6)
o.MSSFix = helpers.OverrideWithUint16(o.MSSFix, other.MSSFix)
o.Interface = helpers.OverrideWithString(o.Interface, other.Interface)
o.Root = helpers.OverrideWithBool(o.Root, other.Root)
o.ProcUser = helpers.OverrideWithString(o.ProcUser, other.ProcUser)
o.Verbosity = helpers.OverrideWithInt(o.Verbosity, other.Verbosity)
o.ProcessUser = helpers.OverrideWithString(o.ProcessUser, other.ProcessUser)
o.Verbosity = helpers.OverrideWithIntPtr(o.Verbosity, other.Verbosity)
o.Flags = helpers.OverrideWithStringSlice(o.Flags, other.Flags)
}
func (o *OpenVPN) setDefaults(vpnProvider string) {
o.Version = helpers.DefaultString(o.Version, constants.Openvpn25)
if vpnProvider == constants.Mullvad {
o.Password = "m"
o.Version = helpers.DefaultString(o.Version, openvpn.Openvpn25)
o.User = helpers.DefaultStringPtr(o.User, "")
if vpnProvider == providers.Mullvad {
o.Password = helpers.DefaultStringPtr(o.Password, "m")
} else {
o.Password = helpers.DefaultStringPtr(o.Password, "")
}
o.ConfFile = helpers.DefaultStringPtr(o.ConfFile, "")
o.Auth = helpers.DefaultStringPtr(o.Auth, "")
o.ClientCrt = helpers.DefaultStringPtr(o.ClientCrt, "")
o.ClientKey = helpers.DefaultStringPtr(o.ClientKey, "")
o.Cert = helpers.DefaultStringPtr(o.Cert, "")
o.Key = helpers.DefaultStringPtr(o.Key, "")
o.EncryptedKey = helpers.DefaultStringPtr(o.EncryptedKey, "")
o.KeyPassphrase = helpers.DefaultStringPtr(o.KeyPassphrase, "")
var defaultEncPreset string
if vpnProvider == constants.PrivateInternetAccess {
defaultEncPreset = constants.PIAEncryptionPresetStrong
if vpnProvider == providers.PrivateInternetAccess {
defaultEncPreset = presets.Strong
}
o.PIAEncPreset = helpers.DefaultStringPtr(o.PIAEncPreset, defaultEncPreset)
o.IPv6 = helpers.DefaultBool(o.IPv6, false)
o.MSSFix = helpers.DefaultUint16(o.MSSFix, 0)
o.Interface = helpers.DefaultString(o.Interface, "tun0")
o.Root = helpers.DefaultBool(o.Root, true)
o.ProcUser = helpers.DefaultString(o.ProcUser, "root")
o.ProcessUser = helpers.DefaultString(o.ProcessUser, "root")
o.Verbosity = helpers.DefaultInt(o.Verbosity, 1)
}
@@ -257,8 +344,8 @@ func (o OpenVPN) String() string {
func (o OpenVPN) toLinesNode() (node *gotree.Node) {
node = gotree.New("OpenVPN settings:")
node.Appendf("OpenVPN version: %s", o.Version)
node.Appendf("User: %s", helpers.ObfuscatePassword(o.User))
node.Appendf("Password: %s", helpers.ObfuscatePassword(o.Password))
node.Appendf("User: %s", helpers.ObfuscatePassword(*o.User))
node.Appendf("Password: %s", helpers.ObfuscatePassword(*o.Password))
if *o.ConfFile != "" {
node.Appendf("Custom configuration file: %s", *o.ConfFile)
@@ -272,12 +359,17 @@ func (o OpenVPN) toLinesNode() (node *gotree.Node) {
node.Appendf("Auth: %s", *o.Auth)
}
if *o.ClientCrt != "" {
node.Appendf("Client crt: %s", helpers.ObfuscateData(*o.ClientCrt))
if *o.Cert != "" {
node.Appendf("Client crt: %s", helpers.ObfuscateData(*o.Cert))
}
if *o.ClientKey != "" {
node.Appendf("Client key: %s", helpers.ObfuscateData(*o.ClientKey))
if *o.Key != "" {
node.Appendf("Client key: %s", helpers.ObfuscateData(*o.Key))
}
if *o.EncryptedKey != "" {
node.Appendf("Encrypted key: %s (key passhrapse %s)",
helpers.ObfuscateData(*o.EncryptedKey), helpers.ObfuscatePassword(*o.KeyPassphrase))
}
if *o.PIAEncPreset != "" {
@@ -294,14 +386,7 @@ func (o OpenVPN) toLinesNode() (node *gotree.Node) {
node.Appendf("Network interface: %s", o.Interface)
}
processUser := "root"
if !*o.Root {
processUser = "some non root user" // TODO
if o.ProcUser != "" {
processUser = o.ProcUser
}
}
node.Appendf("Run OpenVPN as: %s", processUser)
node.Appendf("Run OpenVPN as: %s", o.ProcessUser)
node.Appendf("Verbosity level: %d", *o.Verbosity)

View File

@@ -0,0 +1,44 @@
package settings
import (
"testing"
"github.com/stretchr/testify/assert"
)
func Test_ivpnAccountID(t *testing.T) {
t.Parallel()
testCases := []struct {
s string
match bool
}{
{},
{s: "abc"},
{s: "i"},
{s: "ivpn"},
{s: "ivpn-aaaa"},
{s: "ivpn-aaaa-aaaa"},
{s: "ivpn-aaaa-aaaa-aaa"},
{s: "ivpn-aaaa-aaaa-aaaa", match: true},
{s: "ivpn-aaaa-aaaa-aaaaa"},
{s: "ivpn-a6B7-fP91-Zh6Y", match: true},
{s: "i-aaaa"},
{s: "i-aaaa-aaaa"},
{s: "i-aaaa-aaaa-aaa"},
{s: "i-aaaa-aaaa-aaaa", match: true},
{s: "i-aaaa-aaaa-aaaaa"},
{s: "i-a6B7-fP91-Zh6Y", match: true},
}
for _, testCase := range testCases {
testCase := testCase
t.Run(testCase.s, func(t *testing.T) {
t.Parallel()
match := ivpnAccountID.MatchString(testCase.s)
assert.Equal(t, testCase.match, match)
})
}
}

View File

@@ -4,7 +4,8 @@ import (
"fmt"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/provider/privateinternetaccess/presets"
"github.com/qdm12/gotree"
)
@@ -33,15 +34,17 @@ func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
if confFile := *o.ConfFile; confFile != "" {
err := helpers.FileExists(confFile)
if err != nil {
return fmt.Errorf("%w: %s", ErrOpenVPNConfigFile, err)
return fmt.Errorf("configuration file: %w", err)
}
}
// Validate TCP
if *o.TCP && helpers.IsOneOf(vpnProvider,
constants.Perfectprivacy,
constants.Privado,
constants.Vyprvpn,
providers.Ipvanish,
providers.Perfectprivacy,
providers.Privado,
providers.VPNUnlimited,
providers.Vyprvpn,
) {
return fmt.Errorf("%w: for VPN service provider %s",
ErrOpenVPNTCPNotSupported, vpnProvider)
@@ -51,33 +54,41 @@ func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
if *o.CustomPort != 0 {
switch vpnProvider {
// no restriction on port
case constants.Cyberghost, constants.HideMyAss,
constants.PrivateInternetAccess, constants.Privatevpn,
constants.Protonvpn, constants.Torguard:
case providers.Cyberghost, providers.HideMyAss,
providers.Privatevpn, providers.Torguard:
// no custom port allowed
case constants.Expressvpn, constants.Fastestvpn,
constants.Ipvanish, constants.Nordvpn,
constants.Privado, constants.Purevpn,
constants.Surfshark, constants.VPNUnlimited,
constants.Vyprvpn:
case providers.Expressvpn, providers.Fastestvpn,
providers.Ipvanish, providers.Nordvpn,
providers.Privado, providers.Purevpn,
providers.Surfshark, providers.VPNSecure,
providers.VPNUnlimited, providers.Vyprvpn:
return fmt.Errorf("%w: for VPN service provider %s",
ErrOpenVPNCustomPortNotAllowed, vpnProvider)
default:
var allowedTCP, allowedUDP []uint16
switch vpnProvider {
case constants.Ivpn:
case providers.Ivpn:
allowedTCP = []uint16{80, 443, 1143}
allowedUDP = []uint16{53, 1194, 2049, 2050}
case constants.Mullvad:
case providers.Mullvad:
allowedTCP = []uint16{80, 443, 1401}
allowedUDP = []uint16{53, 1194, 1195, 1196, 1197, 1300, 1301, 1302, 1303, 1400}
case constants.Perfectprivacy:
case providers.Perfectprivacy:
allowedTCP = []uint16{44, 443, 4433}
allowedUDP = []uint16{44, 443, 4433}
case constants.Wevpn:
case providers.PrivateInternetAccess:
allowedTCP = []uint16{80, 110, 443}
allowedUDP = []uint16{53, 1194, 1197, 1198, 8080, 9201}
case providers.Protonvpn:
allowedTCP = []uint16{443, 5995, 8443}
allowedUDP = []uint16{80, 443, 1194, 4569, 5060}
case providers.SlickVPN:
allowedTCP = []uint16{443, 8080, 8888}
allowedUDP = []uint16{443, 8080, 8888}
case providers.Wevpn:
allowedTCP = []uint16{53, 1195, 1199, 2018}
allowedUDP = []uint16{80, 1194, 1198}
case constants.Windscribe:
case providers.Windscribe:
allowedTCP = []uint16{21, 22, 80, 123, 143, 443, 587, 1194, 3306, 8080, 54783}
allowedUDP = []uint16{53, 80, 123, 443, 1194, 54783}
}
@@ -95,11 +106,11 @@ func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
}
// Validate EncPreset
if vpnProvider == constants.PrivateInternetAccess {
if vpnProvider == providers.PrivateInternetAccess {
validEncryptionPresets := []string{
constants.PIAEncryptionPresetNone,
constants.PIAEncryptionPresetNormal,
constants.PIAEncryptionPresetStrong,
presets.None,
presets.Normal,
presets.Strong,
}
if !helpers.IsOneOf(*o.PIAEncPreset, validEncryptionPresets...) {
return fmt.Errorf("%w: %s; valid presets are %s",
@@ -140,8 +151,8 @@ func (o *OpenVPNSelection) setDefaults(vpnProvider string) {
o.CustomPort = helpers.DefaultUint16(o.CustomPort, 0)
var defaultEncPreset string
if vpnProvider == constants.PrivateInternetAccess {
defaultEncPreset = constants.PIAEncryptionPresetStrong
if vpnProvider == providers.PrivateInternetAccess {
defaultEncPreset = presets.Strong
}
o.PIAEncPreset = helpers.DefaultStringPtr(o.PIAEncPreset, defaultEncPreset)
}

View File

@@ -6,7 +6,7 @@ import (
"strings"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gotree"
)
@@ -28,7 +28,7 @@ func (p PortForwarding) validate(vpnProvider string) (err error) {
}
// Validate Enabled
validProviders := []string{constants.PrivateInternetAccess}
validProviders := []string{providers.PrivateInternetAccess}
if !helpers.IsOneOf(vpnProvider, validProviders...) {
return fmt.Errorf("%w: for provider %s, it is only available for %s",
ErrPortForwardingEnabled, vpnProvider, strings.Join(validProviders, ", "))
@@ -38,7 +38,7 @@ func (p PortForwarding) validate(vpnProvider string) (err error) {
if *p.Filepath != "" { // optional
_, err := filepath.Abs(*p.Filepath)
if err != nil {
return fmt.Errorf("%w: %s", ErrPortForwardingFilepathNotValid, err)
return fmt.Errorf("filepath is not valid: %w", err)
}
}

View File

@@ -4,8 +4,8 @@ import (
"fmt"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/constants/vpn"
"github.com/qdm12/gotree"
)
@@ -22,18 +22,18 @@ type Provider struct {
}
// TODO v4 remove pointer for receiver (because of Surfshark).
func (p *Provider) validate(vpnType string, allServers models.AllServers) (err error) {
func (p *Provider) validate(vpnType string, storage Storage) (err error) {
// Validate Name
var validNames []string
if vpnType == constants.OpenVPN {
validNames = constants.AllProviders()
if vpnType == vpn.OpenVPN {
validNames = providers.AllWithCustom()
validNames = append(validNames, "pia") // Retro-compatibility
} else { // Wireguard
validNames = []string{
constants.Custom,
constants.Ivpn,
constants.Mullvad,
constants.Windscribe,
providers.Custom,
providers.Ivpn,
providers.Mullvad,
providers.Windscribe,
}
}
if !helpers.IsOneOf(*p.Name, validNames...) {
@@ -41,14 +41,14 @@ func (p *Provider) validate(vpnType string, allServers models.AllServers) (err e
ErrVPNProviderNameNotValid, *p.Name, helpers.ChoicesOrString(validNames))
}
err = p.ServerSelection.validate(*p.Name, allServers)
err = p.ServerSelection.validate(*p.Name, storage)
if err != nil {
return fmt.Errorf("server selection settings validation failed: %w", err)
return fmt.Errorf("server selection: %w", err)
}
err = p.PortForwarding.validate(*p.Name)
if err != nil {
return fmt.Errorf("port forwarding settings validation failed: %w", err)
return fmt.Errorf("port forwarding: %w", err)
}
return nil
@@ -75,7 +75,7 @@ func (p *Provider) overrideWith(other Provider) {
}
func (p *Provider) setDefaults() {
p.Name = helpers.DefaultStringPtr(p.Name, constants.PrivateInternetAccess)
p.Name = helpers.DefaultStringPtr(p.Name, providers.PrivateInternetAccess)
p.ServerSelection.setDefaults(*p.Name)
p.PortForwarding.setDefaults()
}

View File

@@ -33,7 +33,7 @@ func (p PublicIP) validate() (err error) {
if *p.IPFilepath != "" { // optional
_, err := filepath.Abs(*p.IPFilepath)
if err != nil {
return fmt.Errorf("%w: %s", ErrPublicIPFilepathNotValid, err)
return fmt.Errorf("filepath is not valid: %w", err)
}
}
@@ -48,18 +48,18 @@ func (p *PublicIP) copy() (copied PublicIP) {
}
func (p *PublicIP) mergeWith(other PublicIP) {
p.Period = helpers.MergeWithDuration(p.Period, other.Period)
p.Period = helpers.MergeWithDurationPtr(p.Period, other.Period)
p.IPFilepath = helpers.MergeWithStringPtr(p.IPFilepath, other.IPFilepath)
}
func (p *PublicIP) overrideWith(other PublicIP) {
p.Period = helpers.OverrideWithDuration(p.Period, other.Period)
p.Period = helpers.OverrideWithDurationPtr(p.Period, other.Period)
p.IPFilepath = helpers.OverrideWithStringPtr(p.IPFilepath, other.IPFilepath)
}
func (p *PublicIP) setDefaults() {
const defaultPeriod = 12 * time.Hour
p.Period = helpers.DefaultDuration(p.Period, defaultPeriod)
p.Period = helpers.DefaultDurationPtr(p.Period, defaultPeriod)
p.IPFilepath = helpers.DefaultStringPtr(p.IPFilepath, "/tmp/gluetun/ip")
}

View File

@@ -2,7 +2,9 @@ package settings
import (
"fmt"
"net"
"os"
"strconv"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gotree"
@@ -10,22 +12,30 @@ import (
// ControlServer contains settings to customize the control server operation.
type ControlServer struct {
// Port is the listening port to use.
// It can be set to 0 to bind to a random port.
// Address is the listening address to use.
// It cannot be nil in the internal state.
// TODO change to address
Port *uint16
Address *string
// Log can be true or false to enable logging on requests.
// It cannot be nil in the internal state.
Log *bool
}
func (c ControlServer) validate() (err error) {
_, portStr, err := net.SplitHostPort(*c.Address)
if err != nil {
return fmt.Errorf("listening address is not valid: %w", err)
}
port, err := strconv.Atoi(portStr)
if err != nil {
return fmt.Errorf("listening port it not valid: %w", err)
}
uid := os.Getuid()
const maxPrivilegedPort uint16 = 1023
if uid != 0 && *c.Port <= maxPrivilegedPort {
const maxPrivilegedPort = 1023
if uid != 0 && port != 0 && port <= maxPrivilegedPort {
return fmt.Errorf("%w: %d when running with user ID %d",
ErrControlServerPrivilegedPort, *c.Port, uid)
ErrControlServerPrivilegedPort, port, uid)
}
return nil
@@ -33,15 +43,15 @@ func (c ControlServer) validate() (err error) {
func (c *ControlServer) copy() (copied ControlServer) {
return ControlServer{
Port: helpers.CopyUint16Ptr(c.Port),
Log: helpers.CopyBoolPtr(c.Log),
Address: helpers.CopyStringPtr(c.Address),
Log: helpers.CopyBoolPtr(c.Log),
}
}
// mergeWith merges the other settings into any
// unset field of the receiver settings object.
func (c *ControlServer) mergeWith(other ControlServer) {
c.Port = helpers.MergeWithUint16(c.Port, other.Port)
c.Address = helpers.MergeWithStringPtr(c.Address, other.Address)
c.Log = helpers.MergeWithBool(c.Log, other.Log)
}
@@ -49,13 +59,12 @@ func (c *ControlServer) mergeWith(other ControlServer) {
// settings object with any field set in the other
// settings.
func (c *ControlServer) overrideWith(other ControlServer) {
c.Port = helpers.MergeWithUint16(c.Port, other.Port)
c.Log = helpers.MergeWithBool(c.Log, other.Log)
c.Address = helpers.OverrideWithStringPtr(c.Address, other.Address)
c.Log = helpers.OverrideWithBool(c.Log, other.Log)
}
func (c *ControlServer) setDefaults() {
const defaultPort = 8000
c.Port = helpers.DefaultUint16(c.Port, defaultPort)
c.Address = helpers.DefaultStringPtr(c.Address, ":8000")
c.Log = helpers.DefaultBool(c.Log, true)
}
@@ -65,7 +74,7 @@ func (c ControlServer) String() string {
func (c ControlServer) toLinesNode() (node *gotree.Node) {
node = gotree.New("Control server settings:")
node.Appendf("Listening port: %d", *c.Port)
node.Appendf("Listening address: %s", *c.Address)
node.Appendf("Logging: %s", helpers.BoolPtrToYesNo(c.Log))
return node
}

View File

@@ -1,12 +1,15 @@
package settings
import (
"errors"
"fmt"
"net"
"strings"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/configuration/settings/validation"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/constants/vpn"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gotree"
)
@@ -36,16 +39,20 @@ type ServerSelection struct { //nolint:maligned
Numbers []uint16
// Hostnames is the list of hostnames to filter VPN servers with.
Hostnames []string
// OwnedOnly is true if only VPN provider owned servers
// OwnedOnly is true if VPN provider servers that are not owned
// should be filtered. This is used with Mullvad.
OwnedOnly *bool
// FreeOnly is true if only free VPN servers
// should be filtered. This is used with ProtonVPN.
// FreeOnly is true if VPN servers that are not free should
// be filtered. This is used with ProtonVPN and VPN Unlimited.
FreeOnly *bool
// FreeOnly is true if only free VPN servers
// should be filtered. This is used with ProtonVPN.
// PremiumOnly is true if VPN servers that are not premium should
// be filtered. This is used with VPN Secure.
// TODO extend to providers using FreeOnly.
PremiumOnly *bool
// StreamOnly is true if VPN servers not for streaming should
// be filtered. This is used with VPNUnlimited.
StreamOnly *bool
// MultiHopOnly is true if only multihop VPN servers
// MultiHopOnly is true if VPN servers that are not multihop
// should be filtered. This is used with Surfshark.
MultiHopOnly *bool
@@ -57,190 +64,136 @@ type ServerSelection struct { //nolint:maligned
Wireguard WireguardSelection
}
var (
ErrOwnedOnlyNotSupported = errors.New("owned only filter is not supported")
ErrFreeOnlyNotSupported = errors.New("free only filter is not supported")
ErrPremiumOnlyNotSupported = errors.New("premium only filter is not supported")
ErrStreamOnlyNotSupported = errors.New("stream only filter is not supported")
ErrMultiHopOnlyNotSupported = errors.New("multi hop only filter is not supported")
ErrFreePremiumBothSet = errors.New("free only and premium only filters are both set")
)
func (ss *ServerSelection) validate(vpnServiceProvider string,
allServers models.AllServers) (err error) {
storage Storage) (err error) {
switch ss.VPN {
case constants.OpenVPN, constants.Wireguard:
case vpn.OpenVPN, vpn.Wireguard:
default:
return fmt.Errorf("%w: %s", ErrVPNTypeNotValid, ss.VPN)
}
var countryChoices, regionChoices, cityChoices,
ispChoices, nameChoices, hostnameChoices []string
switch vpnServiceProvider {
case constants.Custom:
case constants.Cyberghost:
servers := allServers.GetCyberghost()
countryChoices = constants.CyberghostCountryChoices(servers)
hostnameChoices = constants.CyberghostHostnameChoices(servers)
case constants.Expressvpn:
servers := allServers.GetExpressvpn()
countryChoices = constants.ExpressvpnCountriesChoices(servers)
cityChoices = constants.ExpressvpnCityChoices(servers)
hostnameChoices = constants.ExpressvpnHostnameChoices(servers)
case constants.Fastestvpn:
servers := allServers.GetFastestvpn()
countryChoices = constants.FastestvpnCountriesChoices(servers)
hostnameChoices = constants.FastestvpnHostnameChoices(servers)
case constants.HideMyAss:
servers := allServers.GetHideMyAss()
countryChoices = constants.HideMyAssCountryChoices(servers)
regionChoices = constants.HideMyAssRegionChoices(servers)
cityChoices = constants.HideMyAssCityChoices(servers)
hostnameChoices = constants.HideMyAssHostnameChoices(servers)
case constants.Ipvanish:
servers := allServers.GetIpvanish()
countryChoices = constants.IpvanishCountryChoices(servers)
cityChoices = constants.IpvanishCityChoices(servers)
hostnameChoices = constants.IpvanishHostnameChoices(servers)
case constants.Ivpn:
servers := allServers.GetIvpn()
countryChoices = constants.IvpnCountryChoices(servers)
cityChoices = constants.IvpnCityChoices(servers)
ispChoices = constants.IvpnISPChoices(servers)
hostnameChoices = constants.IvpnHostnameChoices(servers)
case constants.Mullvad:
servers := allServers.GetMullvad()
countryChoices = constants.MullvadCountryChoices(servers)
cityChoices = constants.MullvadCityChoices(servers)
ispChoices = constants.MullvadISPChoices(servers)
hostnameChoices = constants.MullvadHostnameChoices(servers)
case constants.Nordvpn:
servers := allServers.GetNordvpn()
regionChoices = constants.NordvpnRegionChoices(servers)
hostnameChoices = constants.NordvpnHostnameChoices(servers)
case constants.Perfectprivacy:
servers := allServers.GetPerfectprivacy()
cityChoices = constants.PerfectprivacyCityChoices(servers)
case constants.Privado:
servers := allServers.GetPrivado()
countryChoices = constants.PrivadoCountryChoices(servers)
regionChoices = constants.PrivadoRegionChoices(servers)
cityChoices = constants.PrivadoCityChoices(servers)
hostnameChoices = constants.PrivadoHostnameChoices(servers)
case constants.PrivateInternetAccess:
servers := allServers.GetPia()
regionChoices = constants.PIAGeoChoices(servers)
hostnameChoices = constants.PIAHostnameChoices(servers)
nameChoices = constants.PIANameChoices(servers)
case constants.Privatevpn:
servers := allServers.GetPrivatevpn()
countryChoices = constants.PrivatevpnCountryChoices(servers)
cityChoices = constants.PrivatevpnCityChoices(servers)
hostnameChoices = constants.PrivatevpnHostnameChoices(servers)
case constants.Protonvpn:
servers := allServers.GetProtonvpn()
countryChoices = constants.ProtonvpnCountryChoices(servers)
regionChoices = constants.ProtonvpnRegionChoices(servers)
cityChoices = constants.ProtonvpnCityChoices(servers)
nameChoices = constants.ProtonvpnNameChoices(servers)
hostnameChoices = constants.ProtonvpnHostnameChoices(servers)
case constants.Purevpn:
servers := allServers.GetPurevpn()
countryChoices = constants.PurevpnCountryChoices(servers)
regionChoices = constants.PurevpnRegionChoices(servers)
cityChoices = constants.PurevpnCityChoices(servers)
hostnameChoices = constants.PurevpnHostnameChoices(servers)
case constants.Surfshark:
servers := allServers.GetSurfshark()
countryChoices = constants.SurfsharkCountryChoices(servers)
cityChoices = constants.SurfsharkCityChoices(servers)
hostnameChoices = constants.SurfsharkHostnameChoices(servers)
regionChoices = constants.SurfsharkRegionChoices(servers)
// TODO v4 remove
regionChoices = append(regionChoices, constants.SurfsharkRetroLocChoices(servers)...)
if err := helpers.AreAllOneOf(ss.Regions, regionChoices); err != nil {
return fmt.Errorf("%w: %s", ErrRegionNotValid, err)
}
// Retro compatibility
// TODO remove in v4
*ss = surfsharkRetroRegion(*ss)
case constants.Torguard:
servers := allServers.GetTorguard()
countryChoices = constants.TorguardCountryChoices(servers)
cityChoices = constants.TorguardCityChoices(servers)
hostnameChoices = constants.TorguardHostnameChoices(servers)
case constants.VPNUnlimited:
servers := allServers.GetVPNUnlimited()
countryChoices = constants.VPNUnlimitedCountryChoices(servers)
cityChoices = constants.VPNUnlimitedCityChoices(servers)
hostnameChoices = constants.VPNUnlimitedHostnameChoices(servers)
case constants.Vyprvpn:
servers := allServers.GetVyprvpn()
regionChoices = constants.VyprvpnRegionChoices(servers)
case constants.Wevpn:
servers := allServers.GetWevpn()
cityChoices = constants.WevpnCityChoices(servers)
hostnameChoices = constants.WevpnHostnameChoices(servers)
case constants.Windscribe:
servers := allServers.GetWindscribe()
regionChoices = constants.WindscribeRegionChoices(servers)
cityChoices = constants.WindscribeCityChoices(servers)
hostnameChoices = constants.WindscribeHostnameChoices(servers)
default:
return fmt.Errorf("%w: %s", ErrVPNProviderNameNotValid, vpnServiceProvider)
}
err = validateServerFilters(*ss, countryChoices, regionChoices, cityChoices,
ispChoices, nameChoices, hostnameChoices)
filterChoices, err := getLocationFilterChoices(vpnServiceProvider, ss, storage)
if err != nil {
return err // already wrapped error
}
if ss.VPN == constants.OpenVPN {
err = validateServerFilters(*ss, filterChoices)
if err != nil {
if errors.Is(err, helpers.ErrNoChoice) {
return fmt.Errorf("for VPN service provider %s: %w", vpnServiceProvider, err)
}
return err // already wrapped error
}
if *ss.OwnedOnly &&
vpnServiceProvider != providers.Mullvad {
return fmt.Errorf("%w: for VPN service provider %s",
ErrOwnedOnlyNotSupported, vpnServiceProvider)
}
if *ss.FreeOnly &&
!helpers.IsOneOf(vpnServiceProvider,
providers.Protonvpn,
providers.VPNUnlimited,
) {
return fmt.Errorf("%w: for VPN service provider %s",
ErrFreeOnlyNotSupported, vpnServiceProvider)
}
if *ss.PremiumOnly &&
!helpers.IsOneOf(vpnServiceProvider,
providers.VPNSecure,
) {
return fmt.Errorf("%w: for VPN service provider %s",
ErrPremiumOnlyNotSupported, vpnServiceProvider)
}
if *ss.FreeOnly && *ss.PremiumOnly {
return ErrFreePremiumBothSet
}
if *ss.StreamOnly &&
!helpers.IsOneOf(vpnServiceProvider,
providers.Protonvpn,
providers.VPNUnlimited,
) {
return fmt.Errorf("%w: for VPN service provider %s",
ErrStreamOnlyNotSupported, vpnServiceProvider)
}
if *ss.MultiHopOnly &&
vpnServiceProvider != providers.Surfshark {
return fmt.Errorf("%w: for VPN service provider %s",
ErrMultiHopOnlyNotSupported, vpnServiceProvider)
}
if ss.VPN == vpn.OpenVPN {
err = ss.OpenVPN.validate(vpnServiceProvider)
if err != nil {
return fmt.Errorf("OpenVPN server selection settings validation failed: %w", err)
return fmt.Errorf("OpenVPN server selection settings: %w", err)
}
} else {
err = ss.Wireguard.validate(vpnServiceProvider)
if err != nil {
return fmt.Errorf("Wireguard server selection settings validation failed: %w", err)
return fmt.Errorf("Wireguard server selection settings: %w", err)
}
}
return nil
}
func getLocationFilterChoices(vpnServiceProvider string,
ss *ServerSelection, storage Storage) (filterChoices models.FilterChoices,
err error) {
filterChoices = storage.GetFilterChoices(vpnServiceProvider)
if vpnServiceProvider == providers.Surfshark {
// // Retro compatibility
// TODO v4 remove
filterChoices.Regions = append(filterChoices.Regions, validation.SurfsharkRetroLocChoices()...)
if err := helpers.AreAllOneOf(ss.Regions, filterChoices.Regions); err != nil {
return models.FilterChoices{}, fmt.Errorf("%w: %s", ErrRegionNotValid, err)
}
*ss = surfsharkRetroRegion(*ss)
}
return filterChoices, nil
}
// validateServerFilters validates filters against the choices given as arguments.
// Set an argument to nil to pass the check for a particular filter.
func validateServerFilters(settings ServerSelection,
countryChoices, regionChoices, cityChoices, ispChoices,
nameChoices, hostnameChoices []string) (err error) {
if countryChoices != nil {
if err := helpers.AreAllOneOf(settings.Countries, countryChoices); err != nil {
return fmt.Errorf("%w: %s", ErrCountryNotValid, err)
}
func validateServerFilters(settings ServerSelection, filterChoices models.FilterChoices) (err error) {
if err := helpers.AreAllOneOf(settings.Countries, filterChoices.Countries); err != nil {
return fmt.Errorf("%w: %s", ErrCountryNotValid, err)
}
if regionChoices != nil {
if err := helpers.AreAllOneOf(settings.Regions, regionChoices); err != nil {
return fmt.Errorf("%w: %s", ErrRegionNotValid, err)
}
if err := helpers.AreAllOneOf(settings.Regions, filterChoices.Regions); err != nil {
return fmt.Errorf("%w: %s", ErrRegionNotValid, err)
}
if cityChoices != nil {
if err := helpers.AreAllOneOf(settings.Cities, cityChoices); err != nil {
return fmt.Errorf("%w: %s", ErrCityNotValid, err)
}
if err := helpers.AreAllOneOf(settings.Cities, filterChoices.Cities); err != nil {
return fmt.Errorf("%w: %s", ErrCityNotValid, err)
}
if ispChoices != nil {
if err := helpers.AreAllOneOf(settings.ISPs, ispChoices); err != nil {
return fmt.Errorf("%w: %s", ErrISPNotValid, err)
}
if err := helpers.AreAllOneOf(settings.ISPs, filterChoices.ISPs); err != nil {
return fmt.Errorf("%w: %s", ErrISPNotValid, err)
}
if hostnameChoices != nil {
if err := helpers.AreAllOneOf(settings.Hostnames, hostnameChoices); err != nil {
return fmt.Errorf("%w: %s", ErrHostnameNotValid, err)
}
if err := helpers.AreAllOneOf(settings.Hostnames, filterChoices.Hostnames); err != nil {
return fmt.Errorf("%w: %s", ErrHostnameNotValid, err)
}
if nameChoices != nil {
if err := helpers.AreAllOneOf(settings.Names, nameChoices); err != nil {
return fmt.Errorf("%w: %s", ErrNameNotValid, err)
}
if err := helpers.AreAllOneOf(settings.Names, filterChoices.Names); err != nil {
return fmt.Errorf("%w: %s", ErrNameNotValid, err)
}
return nil
@@ -259,6 +212,7 @@ func (ss *ServerSelection) copy() (copied ServerSelection) {
Numbers: helpers.CopyUint16Slice(ss.Numbers),
OwnedOnly: helpers.CopyBoolPtr(ss.OwnedOnly),
FreeOnly: helpers.CopyBoolPtr(ss.FreeOnly),
PremiumOnly: helpers.CopyBoolPtr(ss.PremiumOnly),
StreamOnly: helpers.CopyBoolPtr(ss.StreamOnly),
MultiHopOnly: helpers.CopyBoolPtr(ss.MultiHopOnly),
OpenVPN: ss.OpenVPN.copy(),
@@ -278,6 +232,7 @@ func (ss *ServerSelection) mergeWith(other ServerSelection) {
ss.Numbers = helpers.MergeUint16Slices(ss.Numbers, other.Numbers)
ss.OwnedOnly = helpers.MergeWithBool(ss.OwnedOnly, other.OwnedOnly)
ss.FreeOnly = helpers.MergeWithBool(ss.FreeOnly, other.FreeOnly)
ss.PremiumOnly = helpers.MergeWithBool(ss.PremiumOnly, other.PremiumOnly)
ss.StreamOnly = helpers.MergeWithBool(ss.StreamOnly, other.StreamOnly)
ss.MultiHopOnly = helpers.MergeWithBool(ss.MultiHopOnly, other.MultiHopOnly)
@@ -297,6 +252,7 @@ func (ss *ServerSelection) overrideWith(other ServerSelection) {
ss.Numbers = helpers.OverrideWithUint16Slice(ss.Numbers, other.Numbers)
ss.OwnedOnly = helpers.OverrideWithBool(ss.OwnedOnly, other.OwnedOnly)
ss.FreeOnly = helpers.OverrideWithBool(ss.FreeOnly, other.FreeOnly)
ss.PremiumOnly = helpers.OverrideWithBool(ss.PremiumOnly, other.PremiumOnly)
ss.StreamOnly = helpers.OverrideWithBool(ss.StreamOnly, other.StreamOnly)
ss.MultiHopOnly = helpers.OverrideWithBool(ss.MultiHopOnly, other.MultiHopOnly)
ss.OpenVPN.overrideWith(other.OpenVPN)
@@ -304,10 +260,11 @@ func (ss *ServerSelection) overrideWith(other ServerSelection) {
}
func (ss *ServerSelection) setDefaults(vpnProvider string) {
ss.VPN = helpers.DefaultString(ss.VPN, constants.OpenVPN)
ss.VPN = helpers.DefaultString(ss.VPN, vpn.OpenVPN)
ss.TargetIP = helpers.DefaultIP(ss.TargetIP, net.IP{})
ss.OwnedOnly = helpers.DefaultBool(ss.OwnedOnly, false)
ss.FreeOnly = helpers.DefaultBool(ss.FreeOnly, false)
ss.PremiumOnly = helpers.DefaultBool(ss.PremiumOnly, false)
ss.StreamOnly = helpers.DefaultBool(ss.StreamOnly, false)
ss.MultiHopOnly = helpers.DefaultBool(ss.MultiHopOnly, false)
ss.OpenVPN.setDefaults(vpnProvider)
@@ -364,6 +321,10 @@ func (ss ServerSelection) toLinesNode() (node *gotree.Node) {
node.Appendf("Free only servers: yes")
}
if *ss.PremiumOnly {
node.Appendf("Premium only servers: yes")
}
if *ss.StreamOnly {
node.Appendf("Stream only servers: yes")
}
@@ -372,7 +333,7 @@ func (ss ServerSelection) toLinesNode() (node *gotree.Node) {
node.Appendf("Multi-hop only servers: yes")
}
if ss.VPN == constants.OpenVPN {
if ss.VPN == vpn.OpenVPN {
node.AppendNode(ss.OpenVPN.toLinesNode())
} else {
node.AppendNode(ss.Wireguard.toLinesNode())

View File

@@ -4,6 +4,7 @@ import (
"fmt"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/pprof"
"github.com/qdm12/gotree"
)
@@ -20,12 +21,17 @@ type Settings struct {
Updater Updater
Version Version
VPN VPN
Pprof pprof.Settings
}
type Storage interface {
GetFilterChoices(provider string) models.FilterChoices
}
// Validate validates all the settings and returns an error
// if one of them is not valid.
// TODO v4 remove pointer for receiver (because of Surfshark).
func (s *Settings) Validate(allServers models.AllServers) (err error) {
func (s *Settings) Validate(storage Storage) (err error) {
nameToValidation := map[string]func() error{
"control server": s.ControlServer.validate,
"dns": s.DNS.validate,
@@ -38,15 +44,16 @@ func (s *Settings) Validate(allServers models.AllServers) (err error) {
"system": s.System.validate,
"updater": s.Updater.Validate,
"version": s.Version.validate,
// Pprof validation done in pprof constructor
"VPN": func() error {
return s.VPN.validate(allServers)
return s.VPN.Validate(storage)
},
}
for name, validation := range nameToValidation {
err = validation()
if err != nil {
return fmt.Errorf("failed validating %s settings: %w", name, err)
return fmt.Errorf("%s settings: %w", name, err)
}
}
@@ -66,7 +73,8 @@ func (s *Settings) copy() (copied Settings) {
System: s.System.copy(),
Updater: s.Updater.copy(),
Version: s.Version.copy(),
VPN: s.VPN.copy(),
VPN: s.VPN.Copy(),
Pprof: s.Pprof.Copy(),
}
}
@@ -83,10 +91,11 @@ func (s *Settings) MergeWith(other Settings) {
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,
allServers models.AllServers) (err error) {
storage Storage) (err error) {
patchedSettings := s.copy()
patchedSettings.ControlServer.overrideWith(other.ControlServer)
patchedSettings.DNS.overrideWith(other.DNS)
@@ -99,8 +108,9 @@ func (s *Settings) OverrideWith(other Settings,
patchedSettings.System.overrideWith(other.System)
patchedSettings.Updater.overrideWith(other.Updater)
patchedSettings.Version.overrideWith(other.Version)
patchedSettings.VPN.overrideWith(other.VPN)
err = patchedSettings.Validate(allServers)
patchedSettings.VPN.OverrideWith(other.VPN)
patchedSettings.Pprof.OverrideWith(other.Pprof)
err = patchedSettings.Validate(storage)
if err != nil {
return err
}
@@ -118,9 +128,10 @@ func (s *Settings) SetDefaults() {
s.PublicIP.setDefaults()
s.Shadowsocks.setDefaults()
s.System.setDefaults()
s.Updater.SetDefaults()
s.Version.setDefaults()
s.VPN.setDefaults()
s.Updater.SetDefaults(*s.VPN.Provider.Name)
s.Pprof.SetDefaults()
}
func (s Settings) String() string {
@@ -142,6 +153,7 @@ func (s Settings) toLinesNode() (node *gotree.Node) {
node.AppendNode(s.PublicIP.toLinesNode())
node.AppendNode(s.Updater.toLinesNode())
node.AppendNode(s.Version.toLinesNode())
node.AppendNode(s.Pprof.ToLinesNode())
return node
}

View File

@@ -66,7 +66,9 @@ func Test_Settings_String(t *testing.T) {
| └── Log level: INFO
├── Health settings:
| ├── Server listening address: 127.0.0.1:9999
| ├── Address to ping: github.com
| ├── Target address: cloudflare.com:443
| ├── Read header timeout: 100ms
| ├── Read timeout: 500ms
| └── VPN wait durations:
| ├── Initial duration: 6s
| └── Additional duration: 5s
@@ -75,7 +77,7 @@ func Test_Settings_String(t *testing.T) {
├── HTTP proxy settings:
| └── Enabled: no
├── Control server settings:
| ├── Listening port: 8000
| ├── Listening address: :8000
| └── Logging: yes
├── OS Alpine settings:
| ├── Process UID: 1000

View File

@@ -3,15 +3,14 @@ package settings
import (
"strings"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/provider/surfshark/servers"
)
func surfsharkRetroRegion(selection ServerSelection) (
updatedSelection ServerSelection) {
locationData := constants.SurfsharkLocationData()
locationData := servers.LocationData()
retroToLocation := make(map[string]models.SurfsharkLocationData, len(locationData))
retroToLocation := make(map[string]servers.ServerLocation, len(locationData))
for _, data := range locationData {
if data.RetroLoc == "" {
continue

View File

@@ -7,8 +7,8 @@ import (
// System contains settings to configure system related elements.
type System struct {
PUID *uint16
PGID *uint16
PUID *uint32
PGID *uint32
Timezone string
}
@@ -19,28 +19,28 @@ func (s System) validate() (err error) {
func (s *System) copy() (copied System) {
return System{
PUID: helpers.CopyUint16Ptr(s.PUID),
PGID: helpers.CopyUint16Ptr(s.PGID),
PUID: helpers.CopyUint32Ptr(s.PUID),
PGID: helpers.CopyUint32Ptr(s.PGID),
Timezone: s.Timezone,
}
}
func (s *System) mergeWith(other System) {
s.PUID = helpers.MergeWithUint16(s.PUID, other.PUID)
s.PGID = helpers.MergeWithUint16(s.PGID, other.PGID)
s.PUID = helpers.MergeWithUint32(s.PUID, other.PUID)
s.PGID = helpers.MergeWithUint32(s.PGID, other.PGID)
s.Timezone = helpers.MergeWithString(s.Timezone, other.Timezone)
}
func (s *System) overrideWith(other System) {
s.PUID = helpers.OverrideWithUint16(s.PUID, other.PUID)
s.PGID = helpers.OverrideWithUint16(s.PGID, other.PGID)
s.PUID = helpers.OverrideWithUint32(s.PUID, other.PUID)
s.PGID = helpers.OverrideWithUint32(s.PGID, other.PGID)
s.Timezone = helpers.OverrideWithString(s.Timezone, other.Timezone)
}
func (s *System) setDefaults() {
const defaultID = 1000
s.PUID = helpers.DefaultUint16(s.PUID, defaultID)
s.PGID = helpers.DefaultUint16(s.PGID, defaultID)
s.PUID = helpers.DefaultUint32(s.PUID, defaultID)
s.PGID = helpers.DefaultUint32(s.PGID, defaultID)
}
func (s System) String() string {

View File

@@ -2,12 +2,11 @@ package settings
import (
"fmt"
"net"
"strings"
"time"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gotree"
)
@@ -21,16 +20,15 @@ type Updater struct {
Period *time.Duration
// DNSAddress is the DNS server address to use
// to resolve VPN server hostnames to IP addresses.
// It cannot be nil in the internal state.
DNSAddress net.IP
// It cannot be the empty string in the internal state.
DNSAddress string
// MinRatio is the minimum ratio of servers to
// find per provider, compared to the total current
// number of servers. It defaults to 0.8.
MinRatio float64
// Providers is the list of VPN service providers
// to update server information for.
Providers []string
// CLI is to precise the updater is running in CLI
// mode. This is set automatically and cannot be set
// by settings sources. It cannot be nil in the
// internal state.
CLI *bool
}
func (u Updater) Validate() (err error) {
@@ -40,21 +38,23 @@ func (u Updater) Validate() (err error) {
ErrUpdaterPeriodTooSmall, *u.Period, minPeriod)
}
for i, provider := range u.Providers {
valid := false
for _, validProvider := range constants.AllProviders() {
if validProvider == constants.Custom {
continue
}
if u.MinRatio <= 0 || u.MinRatio > 1 {
return fmt.Errorf("%w: %.2f must be between 0+ and 1",
ErrMinRatioNotValid, u.MinRatio)
}
validProviders := providers.All()
for _, provider := range u.Providers {
valid := false
for _, validProvider := range validProviders {
if provider == validProvider {
valid = true
break
}
}
if !valid {
return fmt.Errorf("%w: %s at index %d",
ErrVPNProviderNameNotValid, provider, i)
return fmt.Errorf("%w: %q can only be one of %s",
ErrVPNProviderNameNotValid, provider, helpers.ChoicesOrString(validProviders))
}
}
@@ -64,35 +64,43 @@ func (u Updater) Validate() (err error) {
func (u *Updater) copy() (copied Updater) {
return Updater{
Period: helpers.CopyDurationPtr(u.Period),
DNSAddress: helpers.CopyIP(u.DNSAddress),
DNSAddress: u.DNSAddress,
MinRatio: u.MinRatio,
Providers: helpers.CopyStringSlice(u.Providers),
CLI: u.CLI,
}
}
// mergeWith merges the other settings into any
// unset field of the receiver settings object.
func (u *Updater) mergeWith(other Updater) {
u.Period = helpers.MergeWithDuration(u.Period, other.Period)
u.DNSAddress = helpers.MergeWithIP(u.DNSAddress, other.DNSAddress)
u.Period = helpers.MergeWithDurationPtr(u.Period, other.Period)
u.DNSAddress = helpers.MergeWithString(u.DNSAddress, other.DNSAddress)
u.MinRatio = helpers.MergeWithFloat64(u.MinRatio, other.MinRatio)
u.Providers = helpers.MergeStringSlices(u.Providers, other.Providers)
u.CLI = helpers.MergeWithBool(u.CLI, other.CLI)
}
// overrideWith overrides fields of the receiver
// settings object with any field set in the other
// settings.
func (u *Updater) overrideWith(other Updater) {
u.Period = helpers.OverrideWithDuration(u.Period, other.Period)
u.DNSAddress = helpers.OverrideWithIP(u.DNSAddress, other.DNSAddress)
u.Period = helpers.OverrideWithDurationPtr(u.Period, other.Period)
u.DNSAddress = helpers.OverrideWithString(u.DNSAddress, other.DNSAddress)
u.MinRatio = helpers.OverrideWithFloat64(u.MinRatio, other.MinRatio)
u.Providers = helpers.OverrideWithStringSlice(u.Providers, other.Providers)
u.CLI = helpers.MergeWithBool(u.CLI, other.CLI)
}
func (u *Updater) SetDefaults() {
u.Period = helpers.DefaultDuration(u.Period, 0)
u.DNSAddress = helpers.DefaultIP(u.DNSAddress, net.IPv4(1, 1, 1, 1))
u.CLI = helpers.DefaultBool(u.CLI, false)
func (u *Updater) SetDefaults(vpnProvider string) {
u.Period = helpers.DefaultDurationPtr(u.Period, 0)
u.DNSAddress = helpers.DefaultString(u.DNSAddress, "1.1.1.1:53")
if u.MinRatio == 0 {
const defaultMinRatio = 0.8
u.MinRatio = defaultMinRatio
}
if len(u.Providers) == 0 && vpnProvider != providers.Custom {
u.Providers = []string{vpnProvider}
}
}
func (u Updater) String() string {
@@ -100,18 +108,15 @@ func (u Updater) String() string {
}
func (u Updater) toLinesNode() (node *gotree.Node) {
if *u.Period == 0 {
if *u.Period == 0 || len(u.Providers) == 0 {
return nil
}
node = gotree.New("Server data updater settings:")
node.Appendf("Update period: %s", *u.Period)
node.Appendf("DNS address: %s", u.DNSAddress)
node.Appendf("Minimum ratio: %.1f", u.MinRatio)
node.Appendf("Providers to update: %s", strings.Join(u.Providers, ", "))
if *u.CLI {
node.Appendf("CLI mode: enabled")
}
return node
}

View File

@@ -0,0 +1,129 @@
package validation
import (
"sort"
"github.com/qdm12/gluetun/internal/models"
)
func sortedInsert(ss []string, s string) []string {
i := sort.SearchStrings(ss, s)
ss = append(ss, "")
copy(ss[i+1:], ss[i:])
ss[i] = s
return ss
}
func ExtractCountries(servers []models.Server) (values []string) {
seen := make(map[string]struct{}, len(servers))
values = make([]string, 0, len(servers))
for _, server := range servers {
value := server.Country
if value == "" {
continue
}
_, alreadySeen := seen[value]
if alreadySeen {
continue
}
seen[value] = struct{}{}
values = sortedInsert(values, value)
}
return values
}
func ExtractRegions(servers []models.Server) (values []string) {
seen := make(map[string]struct{}, len(servers))
values = make([]string, 0, len(servers))
for _, server := range servers {
value := server.Region
if value == "" {
continue
}
_, alreadySeen := seen[value]
if alreadySeen {
continue
}
seen[value] = struct{}{}
values = sortedInsert(values, value)
}
return values
}
func ExtractCities(servers []models.Server) (values []string) {
seen := make(map[string]struct{}, len(servers))
values = make([]string, 0, len(servers))
for _, server := range servers {
value := server.City
if value == "" {
continue
}
_, alreadySeen := seen[value]
if alreadySeen {
continue
}
seen[value] = struct{}{}
values = sortedInsert(values, value)
}
return values
}
func ExtractISPs(servers []models.Server) (values []string) {
seen := make(map[string]struct{}, len(servers))
values = make([]string, 0, len(servers))
for _, server := range servers {
value := server.ISP
if value == "" {
continue
}
_, alreadySeen := seen[value]
if alreadySeen {
continue
}
seen[value] = struct{}{}
values = sortedInsert(values, value)
}
return values
}
func ExtractServerNames(servers []models.Server) (values []string) {
seen := make(map[string]struct{}, len(servers))
values = make([]string, 0, len(servers))
for _, server := range servers {
value := server.ServerName
if value == "" {
continue
}
_, alreadySeen := seen[value]
if alreadySeen {
continue
}
seen[value] = struct{}{}
values = sortedInsert(values, value)
}
return values
}
func ExtractHostnames(servers []models.Server) (values []string) {
seen := make(map[string]struct{}, len(servers))
values = make([]string, 0, len(servers))
for _, server := range servers {
value := server.Hostname
if value == "" {
continue
}
_, alreadySeen := seen[value]
if alreadySeen {
continue
}
seen[value] = struct{}{}
values = sortedInsert(values, value)
}
return values
}

View File

@@ -0,0 +1,21 @@
package validation
import (
"github.com/qdm12/gluetun/internal/provider/surfshark/servers"
)
// TODO remove in v4.
func SurfsharkRetroLocChoices() (choices []string) {
locationData := servers.LocationData()
choices = make([]string, 0, len(locationData))
seen := make(map[string]struct{}, len(locationData))
for _, data := range locationData {
if _, ok := seen[data.RetroLoc]; ok {
continue
}
seen[data.RetroLoc] = struct{}{}
choices = sortedInsert(choices, data.RetroLoc)
}
return choices
}

View File

@@ -5,8 +5,7 @@ import (
"strings"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/constants/vpn"
"github.com/qdm12/gotree"
)
@@ -21,35 +20,35 @@ type VPN struct {
}
// TODO v4 remove pointer for receiver (because of Surfshark).
func (v *VPN) validate(allServers models.AllServers) (err error) {
func (v *VPN) Validate(storage Storage) (err error) {
// Validate Type
validVPNTypes := []string{constants.OpenVPN, constants.Wireguard}
validVPNTypes := []string{vpn.OpenVPN, vpn.Wireguard}
if !helpers.IsOneOf(v.Type, validVPNTypes...) {
return fmt.Errorf("%w: %q and can only be one of %s",
ErrVPNTypeNotValid, v.Type, strings.Join(validVPNTypes, ", "))
}
err = v.Provider.validate(v.Type, allServers)
err = v.Provider.validate(v.Type, storage)
if err != nil {
return fmt.Errorf("provider settings validation failed: %w", err)
return fmt.Errorf("provider settings: %w", err)
}
if v.Type == constants.OpenVPN {
if v.Type == vpn.OpenVPN {
err := v.OpenVPN.validate(*v.Provider.Name)
if err != nil {
return fmt.Errorf("OpenVPN settings validation failed: %w", err)
return fmt.Errorf("OpenVPN settings: %w", err)
}
} else {
err := v.Wireguard.validate(*v.Provider.Name)
if err != nil {
return fmt.Errorf("Wireguard settings validation failed: %w", err)
return fmt.Errorf("Wireguard settings: %w", err)
}
}
return nil
}
func (v *VPN) copy() (copied VPN) {
func (v *VPN) Copy() (copied VPN) {
return VPN{
Type: v.Type,
Provider: v.Provider.copy(),
@@ -65,7 +64,7 @@ func (v *VPN) mergeWith(other VPN) {
v.Wireguard.mergeWith(other.Wireguard)
}
func (v *VPN) overrideWith(other VPN) {
func (v *VPN) OverrideWith(other VPN) {
v.Type = helpers.OverrideWithString(v.Type, other.Type)
v.Provider.overrideWith(other.Provider)
v.OpenVPN.overrideWith(other.OpenVPN)
@@ -73,7 +72,7 @@ func (v *VPN) overrideWith(other VPN) {
}
func (v *VPN) setDefaults() {
v.Type = helpers.DefaultString(v.Type, constants.OpenVPN)
v.Type = helpers.DefaultString(v.Type, vpn.OpenVPN)
v.Provider.setDefaults()
v.OpenVPN.setDefaults(*v.Provider.Name)
v.Wireguard.setDefaults()
@@ -88,7 +87,7 @@ func (v VPN) toLinesNode() (node *gotree.Node) {
node.AppendNode(v.Provider.toLinesNode())
if v.Type == constants.OpenVPN {
if v.Type == vpn.OpenVPN {
node.AppendNode(v.OpenVPN.toLinesNode())
} else {
node.AppendNode(v.Wireguard.toLinesNode())

View File

@@ -6,7 +6,7 @@ import (
"regexp"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gotree"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
)
@@ -35,10 +35,10 @@ var regexpInterfaceName = regexp.MustCompile(`^[a-zA-Z0-9_]+$`)
// It should only be ran if the VPN type chosen is Wireguard.
func (w Wireguard) validate(vpnProvider string) (err error) {
if !helpers.IsOneOf(vpnProvider,
constants.Custom,
constants.Ivpn,
constants.Mullvad,
constants.Windscribe,
providers.Custom,
providers.Ivpn,
providers.Mullvad,
providers.Windscribe,
) {
// do not validate for VPN provider not supporting Wireguard
return nil
@@ -50,14 +50,14 @@ func (w Wireguard) validate(vpnProvider string) (err error) {
}
_, err = wgtypes.ParseKey(*w.PrivateKey)
if err != nil {
return fmt.Errorf("%w: %s", ErrWireguardPrivateKeyNotValid, err)
return fmt.Errorf("private key is not valid: %w", err)
}
// Validate PreSharedKey
if *w.PreSharedKey != "" { // Note: this is optional
_, err = wgtypes.ParseKey(*w.PreSharedKey)
if err != nil {
return fmt.Errorf("%w: %s", ErrWireguardPreSharedKeyNotValid, err)
return fmt.Errorf("pre-shared key is not valid: %w", err)
}
}

View File

@@ -5,7 +5,7 @@ import (
"net"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gotree"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
)
@@ -36,8 +36,8 @@ type WireguardSelection struct {
func (w WireguardSelection) validate(vpnProvider string) (err error) {
// Validate EndpointIP
switch vpnProvider {
case constants.Ivpn, constants.Mullvad, constants.Windscribe: // endpoint IP addresses are baked in
case constants.Custom:
case providers.Ivpn, providers.Mullvad, providers.Windscribe: // endpoint IP addresses are baked in
case providers.Custom:
if len(w.EndpointIP) == 0 {
return ErrWireguardEndpointIPNotSet
}
@@ -47,23 +47,23 @@ func (w WireguardSelection) validate(vpnProvider string) (err error) {
// Validate EndpointPort
switch vpnProvider {
// EndpointPort is required
case constants.Custom:
case providers.Custom:
if *w.EndpointPort == 0 {
return ErrWireguardEndpointPortNotSet
}
case constants.Ivpn, constants.Mullvad, constants.Windscribe:
case providers.Ivpn, providers.Mullvad, providers.Windscribe:
// EndpointPort is optional and can be 0
if *w.EndpointPort == 0 {
break // no custom endpoint port set
}
if vpnProvider == constants.Mullvad {
if vpnProvider == providers.Mullvad {
break // no restriction on custom endpoint port value
}
var allowed []uint16
switch vpnProvider {
case constants.Ivpn:
case providers.Ivpn:
allowed = []uint16{2049, 2050, 53, 30587, 41893, 48574, 58237}
case constants.Windscribe:
case providers.Windscribe:
allowed = []uint16{53, 80, 123, 443, 1194, 65142}
}
@@ -78,8 +78,8 @@ func (w WireguardSelection) validate(vpnProvider string) (err error) {
// Validate PublicKey
switch vpnProvider {
case constants.Ivpn, constants.Mullvad, constants.Windscribe: // public keys are baked in
case constants.Custom:
case providers.Ivpn, providers.Mullvad, providers.Windscribe: // public keys are baked in
case providers.Custom:
if w.PublicKey == "" {
return ErrWireguardPublicKeyNotSet
}

View File

@@ -3,7 +3,6 @@ package env
import (
"fmt"
"net"
"os"
"github.com/qdm12/gluetun/internal/configuration/settings"
)
@@ -21,26 +20,26 @@ func (r *Reader) readDNS() (dns settings.DNS, err error) {
dns.DoT, err = r.readDoT()
if err != nil {
return dns, fmt.Errorf("cannot read DoT settings: %w", err)
return dns, fmt.Errorf("DoT settings: %w", err)
}
return dns, nil
}
func (r *Reader) readDNSServerAddress() (address net.IP, err error) {
s := os.Getenv("DNS_PLAINTEXT_ADDRESS")
key, s := r.getEnvWithRetro("DNS_ADDRESS", "DNS_PLAINTEXT_ADDRESS")
if s == "" {
return nil, nil
}
address = net.ParseIP(s)
if address == nil {
return nil, fmt.Errorf("environment variable DNS_PLAINTEXT_ADDRESS: %w: %s", ErrIPAddressParse, s)
return nil, fmt.Errorf("environment variable %s: %w: %s", key, ErrIPAddressParse, s)
}
// TODO remove in v4
if !address.Equal(net.IPv4(127, 0, 0, 1)) { //nolint:gomnd
r.warner.Warn("DNS_PLAINTEXT_ADDRESS is set to " + s +
r.warner.Warn(key + " is set to " + s +
" 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 server." +
" If the DoT server fails to start, the IPv4 address of the first plaintext DNS server" +

View File

@@ -5,6 +5,7 @@ import (
"fmt"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/govalid/binary"
"inet.af/netaddr"
)
@@ -16,7 +17,7 @@ func (r *Reader) readDNSBlacklist() (blacklist settings.DNSBlacklist, err error)
blacklist.BlockSurveillance, err = r.readBlockSurveillance()
if err != nil {
return blacklist, fmt.Errorf("environment variable BLOCK_MALICIOUS: %w", err)
return blacklist, err
}
blacklist.BlockAds, err = envToBoolPtr("BLOCK_ADS")
@@ -36,22 +37,18 @@ func (r *Reader) readDNSBlacklist() (blacklist settings.DNSBlacklist, err error)
}
func (r *Reader) readBlockSurveillance() (blocked *bool, err error) {
blocked, err = envToBoolPtr("BLOCK_SURVEILLANCE")
if err != nil {
return nil, fmt.Errorf("environment variable BLOCK_SURVEILLANCE: %w", err)
} else if blocked != nil {
return blocked, nil
key, value := r.getEnvWithRetro("BLOCK_SURVEILLANCE", "BLOCK_NSA")
if value == "" {
return nil, nil //nolint:nilnil
}
blocked, err = envToBoolPtr("BLOCK_NSA")
blocked = new(bool)
*blocked, err = binary.Validate(value)
if err != nil {
return nil, fmt.Errorf("environment variable BLOCK_NSA: %w", err)
} else if blocked != nil {
r.onRetroActive("BLOCK_NSA", "BLOCK_SURVEILLANCE")
return blocked, nil
return nil, fmt.Errorf("environment variable %s: %w", key, err)
}
return nil, nil //nolint:nilnil
return blocked, nil
}
var (

View File

@@ -22,16 +22,8 @@ func (r *Reader) readFirewall() (firewall settings.Firewall, err error) {
return firewall, fmt.Errorf("environment variable FIREWALL_INPUT_PORTS: %w", err)
}
outboundSubnetsKey := "FIREWALL_OUTBOUND_SUBNETS"
outboundSubnetsKey, _ := r.getEnvWithRetro("FIREWALL_OUTBOUND_SUBNETS", "EXTRA_SUBNETS")
outboundSubnetStrings := envToCSV(outboundSubnetsKey)
if len(outboundSubnetStrings) == 0 {
// Retro-compatibility
outboundSubnetStrings = envToCSV("EXTRA_SUBNETS")
if len(outboundSubnetStrings) > 0 {
outboundSubnetsKey = "EXTRA_SUBNETS"
r.onRetroActive("EXTRA_SUBNETS", "FIREWALL_OUTBOUND_SUBNETS")
}
}
firewall.OutboundSubnets, err = stringsToIPNets(outboundSubnetStrings)
if err != nil {
return firewall, fmt.Errorf("environment variable %s: %w", outboundSubnetsKey, err)
@@ -63,8 +55,7 @@ func stringsToPorts(ss []string) (ports []uint16, err error) {
for i, s := range ss {
port, err := strconv.Atoi(s)
if err != nil {
return nil, fmt.Errorf("%w: %s: %s",
ErrPortParsing, s, err)
return nil, fmt.Errorf("%w: %s: %s", ErrPortParsing, s, err)
} else if port < 1 || port > 65535 {
return nil, fmt.Errorf("%w: must be between 1 and 65535: %d",
ErrPortValue, port)
@@ -74,10 +65,6 @@ func stringsToPorts(ss []string) (ports []uint16, err error) {
return ports, nil
}
var (
ErrIPNetParsing = errors.New("cannot parse IP network")
)
func stringsToIPNets(ss []string) (ipNets []net.IPNet, err error) {
if len(ss) == 0 {
return nil, nil
@@ -86,8 +73,7 @@ func stringsToIPNets(ss []string) (ipNets []net.IPNet, err error) {
for i, s := range ss {
ip, ipNet, err := net.ParseCIDR(s)
if err != nil {
return nil, fmt.Errorf("%w: %s: %s",
ErrIPNetParsing, s, err)
return nil, fmt.Errorf("cannot parse IP network %q: %w", s, err)
}
ipNet.IP = ip
ipNets[i] = *ipNet

View File

@@ -2,15 +2,14 @@ package env
import (
"fmt"
"os"
"time"
"github.com/qdm12/gluetun/internal/configuration/settings"
)
func (r *Reader) ReadHealth() (health settings.Health, err error) {
health.ServerAddress = os.Getenv("HEALTH_SERVER_ADDRESS")
health.AddressToPing = os.Getenv("HEALTH_ADDRESS_TO_PING")
health.ServerAddress = getCleanedEnv("HEALTH_SERVER_ADDRESS")
_, health.TargetAddress = r.getEnvWithRetro("HEALTH_TARGET_ADDRESS", "HEALTH_ADDRESS_TO_PING")
health.VPN.Initial, err = r.readDurationWithRetro(
"HEALTH_VPN_DURATION_INITIAL",
@@ -19,7 +18,7 @@ func (r *Reader) ReadHealth() (health settings.Health, err error) {
return health, err
}
health.VPN.Initial, err = r.readDurationWithRetro(
health.VPN.Addition, err = r.readDurationWithRetro(
"HEALTH_VPN_DURATION_ADDITION",
"HEALTH_OPENVPN_DURATION_ADDITION")
if err != nil {
@@ -30,22 +29,15 @@ func (r *Reader) ReadHealth() (health settings.Health, err error) {
}
func (r *Reader) readDurationWithRetro(envKey, retroEnvKey string) (d *time.Duration, err error) {
s := os.Getenv(envKey)
envKey, s := r.getEnvWithRetro(envKey, retroEnvKey)
if s == "" {
s = os.Getenv(retroEnvKey)
if s == "" {
return nil, nil //nolint:nilnil
}
r.onRetroActive(envKey, retroEnvKey)
envKey = retroEnvKey
return nil, nil //nolint:nilnil
}
d = new(time.Duration)
*d, err = time.ParseDuration(s)
if err != nil {
return nil, fmt.Errorf(
"environment variable %s: %w",
envKey, err)
return nil, fmt.Errorf("environment variable %s: %w", envKey, err)
}
return d, nil

View File

@@ -1,8 +1,6 @@
package env
import (
"encoding/base64"
"errors"
"fmt"
"os"
"strconv"
@@ -13,16 +11,43 @@ import (
"github.com/qdm12/govalid/integer"
)
// getCleanedEnv returns an environment variable value with
// surrounding spaces and trailing new line characters removed.
func getCleanedEnv(envKey string) (value string) {
value = os.Getenv(envKey)
value = strings.TrimSpace(value)
value = strings.TrimSuffix(value, "\r\n")
value = strings.TrimSuffix(value, "\n")
return value
}
func envToCSV(envKey string) (values []string) {
csv := os.Getenv(envKey)
csv := getCleanedEnv(envKey)
if csv == "" {
return nil
}
return lowerAndSplit(csv)
}
func envToInt(envKey string) (n int, err error) {
s := getCleanedEnv(envKey)
if s == "" {
return 0, nil
}
return strconv.Atoi(s)
}
func envToFloat64(envKey string) (f float64, err error) {
s := getCleanedEnv(envKey)
if s == "" {
return 0, nil
}
const bits = 64
return strconv.ParseFloat(s, bits)
}
func envToStringPtr(envKey string) (stringPtr *string) {
s := os.Getenv(envKey)
s := getCleanedEnv(envKey)
if s == "" {
return nil
}
@@ -30,7 +55,7 @@ func envToStringPtr(envKey string) (stringPtr *string) {
}
func envToBoolPtr(envKey string) (boolPtr *bool, err error) {
s := os.Getenv(envKey)
s := getCleanedEnv(envKey)
if s == "" {
return nil, nil //nolint:nilnil
}
@@ -42,7 +67,7 @@ func envToBoolPtr(envKey string) (boolPtr *bool, err error) {
}
func envToIntPtr(envKey string) (intPtr *int, err error) {
s := os.Getenv(envKey)
s := getCleanedEnv(envKey)
if s == "" {
return nil, nil //nolint:nilnil
}
@@ -54,7 +79,7 @@ func envToIntPtr(envKey string) (intPtr *int, err error) {
}
func envToUint8Ptr(envKey string) (uint8Ptr *uint8, err error) {
s := os.Getenv(envKey)
s := getCleanedEnv(envKey)
if s == "" {
return nil, nil //nolint:nilnil
}
@@ -71,7 +96,7 @@ func envToUint8Ptr(envKey string) (uint8Ptr *uint8, err error) {
}
func envToUint16Ptr(envKey string) (uint16Ptr *uint16, err error) {
s := os.Getenv(envKey)
s := getCleanedEnv(envKey)
if s == "" {
return nil, nil //nolint:nilnil
}
@@ -88,7 +113,7 @@ func envToUint16Ptr(envKey string) (uint16Ptr *uint16, err error) {
}
func envToDurationPtr(envKey string) (durationPtr *time.Duration, err error) {
s := os.Getenv(envKey)
s := getCleanedEnv(envKey)
if s == "" {
return nil, nil //nolint:nilnil
}
@@ -107,17 +132,6 @@ func lowerAndSplit(csv string) (values []string) {
return strings.Split(csv, ",")
}
var ErrDecodeBase64 = errors.New("cannot decode base64 string")
func decodeBase64(b64String string) (decoded string, err error) {
b, err := base64.StdEncoding.DecodeString(b64String)
if err != nil {
return "", fmt.Errorf("%w: %s: %s",
ErrDecodeBase64, b64String, err)
}
return string(b), nil
}
func unsetEnvKeys(envKeys []string, err error) (newErr error) {
newErr = err
for _, envKey := range envKeys {
@@ -130,5 +144,5 @@ func unsetEnvKeys(envKeys []string, err error) (newErr error) {
}
func stringPtr(s string) *string { return &s }
func uint16Ptr(n uint16) *uint16 { return &n }
func uint32Ptr(n uint32) *uint32 { return &n }
func boolPtr(b bool) *bool { return &b }

View File

@@ -2,8 +2,6 @@ package env
import (
"fmt"
"os"
"strings"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/govalid/binary"
@@ -33,138 +31,61 @@ func (r *Reader) readHTTPProxy() (httpProxy settings.HTTPProxy, err error) {
}
func (r *Reader) readHTTProxyUser() (user *string) {
s := os.Getenv("HTTPPROXY_USER")
_, s := r.getEnvWithRetro("HTTPPROXY_USER", "PROXY_USER", "TINYPROXY_USER")
if s != "" {
return &s
}
// Retro-compatibility
s = os.Getenv("TINYPROXY_USER")
if s != "" {
r.onRetroActive("TINYPROXY_USER", "HTTPPROXY_USER")
return &s
}
// Retro-compatibility
s = os.Getenv("PROXY_USER")
if s != "" {
r.onRetroActive("PROXY_USER", "HTTPPROXY_USER")
return &s
}
return nil
}
func (r *Reader) readHTTProxyPassword() (user *string) {
s := os.Getenv("HTTPPROXY_PASSWORD")
_, s := r.getEnvWithRetro("HTTPPROXY_PASSWORD", "PROXY_PASSWORD", "TINYPROXY_PASSWORD")
if s != "" {
return &s
}
// Retro-compatibility
s = os.Getenv("TINYPROXY_PASSWORD")
if s != "" {
r.onRetroActive("TINYPROXY_PASSWORD", "HTTPPROXY_PASSWORD")
return &s
}
// Retro-compatibility
s = os.Getenv("PROXY_PASSWORD")
if s != "" {
r.onRetroActive("PROXY_PASSWORD", "HTTPPROXY_PASSWORD")
return &s
}
return nil
}
func (r *Reader) readHTTProxyListeningAddress() (listeningAddress string) {
// Retro-compatibility
retroKeys := []string{"PROXY_PORT", "TINYPROXY_PORT", "HTTPPROXY_PORT"}
for _, retroKey := range retroKeys {
s := os.Getenv(retroKey)
if s != "" {
r.onRetroActive(retroKey, "HTTPPROXY_LISTENING_ADDRESS")
return ":" + s
}
key, value := r.getEnvWithRetro("HTTPPROXY_LISTENING_ADDRESS", "PROXY_PORT", "TINYPROXY_PORT", "HTTPPROXY_PORT")
if key == "HTTPPROXY_LISTENING_ADDRESS" {
return value
}
return os.Getenv("HTTPPROXY_LISTENING_ADDRESS")
return ":" + value
}
func (r *Reader) readHTTProxyEnabled() (enabled *bool, err error) {
s := strings.ToLower(os.Getenv("HTTPPROXY"))
if s != "" {
enabled = new(bool)
*enabled, err = binary.Validate(s)
if err != nil {
return nil, fmt.Errorf("environment variable HTTPPROXY: %w", err)
}
return enabled, nil
key, s := r.getEnvWithRetro("HTTPPROXY", "PROXY", "TINYPROXY")
if s == "" {
return nil, nil //nolint:nilnil
}
// Retro-compatibility
s = strings.ToLower(os.Getenv("TINYPROXY"))
if s != "" {
r.onRetroActive("TINYPROXY", "HTTPPROXY")
enabled = new(bool)
*enabled, err = binary.Validate(s)
if err != nil {
return nil, fmt.Errorf("environment variable TINYPROXY: %w", err)
}
return enabled, nil
enabled = new(bool)
*enabled, err = binary.Validate(s)
if err != nil {
return nil, fmt.Errorf("environment variable %s: %w", key, err)
}
// Retro-compatibility
s = strings.ToLower(os.Getenv("PROXY"))
if s != "" {
r.onRetroActive("PROXY", "HTTPPROXY")
enabled = new(bool)
*enabled, err = binary.Validate(s)
if err != nil {
return nil, fmt.Errorf("environment variable PROXY: %w", err)
}
return enabled, nil
}
return nil, nil //nolint:nilnil
return enabled, nil
}
func (r *Reader) readHTTProxyLog() (enabled *bool, err error) {
s := strings.ToLower(os.Getenv("HTTPPROXY_LOG"))
if s != "" {
enabled = new(bool)
*enabled, err = binary.Validate(s)
if err != nil {
return nil, fmt.Errorf("environment variable HTTPPROXY_LOG: %w", err)
}
return enabled, nil
key, s := r.getEnvWithRetro("HTTPPROXY_LOG", "PROXY_LOG_LEVEL", "TINYPROXY_LOG")
if s == "" {
return nil, nil //nolint:nilnil
}
// Retro-compatibility
retroOption := binary.OptionEnabled("on", "info", "connect", "notice")
s = strings.ToLower(os.Getenv("TINYPROXY_LOG"))
if s != "" {
r.onRetroActive("TINYPROXY_LOG", "HTTPPROXY_LOG")
enabled = new(bool)
*enabled, err = binary.Validate(s, retroOption)
if err != nil {
return nil, fmt.Errorf("environment variable TINYPROXY_LOG: %w", err)
}
return enabled, nil
var binaryOptions []binary.Option
if key != "HTTPROXY_LOG" {
retroOption := binary.OptionEnabled("on", "info", "connect", "notice")
binaryOptions = append(binaryOptions, retroOption)
}
// Retro-compatibility
s = strings.ToLower(os.Getenv("PROXY_LOG_LEVEL"))
if s != "" {
r.onRetroActive("PROXY_LOG_LEVEL", "HTTPPROXY_LOG")
enabled = new(bool)
*enabled, err = binary.Validate(s, retroOption)
if err != nil {
return nil, fmt.Errorf("environment variable PROXY_LOG_LEVEL: %w", err)
}
return enabled, nil
enabled = new(bool)
*enabled, err = binary.Validate(s, binaryOptions...)
if err != nil {
return nil, fmt.Errorf("environment variable %s: %w", key, err)
}
return nil, nil //nolint:nilnil
return enabled, nil
}

View File

@@ -3,11 +3,10 @@ package env
import (
"errors"
"fmt"
"os"
"strings"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/golibs/logging"
"github.com/qdm12/log"
)
func readLog() (log settings.Log, err error) {
@@ -19,13 +18,13 @@ func readLog() (log settings.Log, err error) {
return log, nil
}
func readLogLevel() (level *logging.Level, err error) {
s := os.Getenv("LOG_LEVEL")
func readLogLevel() (level *log.Level, err error) {
s := getCleanedEnv("LOG_LEVEL")
if s == "" {
return nil, nil //nolint:nilnil
}
level = new(logging.Level)
level = new(log.Level)
*level, err = parseLogLevel(s)
if err != nil {
return nil, fmt.Errorf("environment variable LOG_LEVEL: %w", err)
@@ -36,19 +35,19 @@ func readLogLevel() (level *logging.Level, err error) {
var ErrLogLevelUnknown = errors.New("log level is unknown")
func parseLogLevel(s string) (level logging.Level, err error) {
func parseLogLevel(s string) (level log.Level, err error) {
switch strings.ToLower(s) {
case "debug":
return logging.LevelDebug, nil
return log.LevelDebug, nil
case "info":
return logging.LevelInfo, nil
return log.LevelInfo, nil
case "warning":
return logging.LevelWarn, nil
return log.LevelWarn, nil
case "error":
return logging.LevelError, nil
return log.LevelError, nil
default:
return level, fmt.Errorf(
"%w: %s: can be one of: debug, info, warning or error",
"%w: %q is not valid and can be one of debug, info, warning or error",
ErrLogLevelUnknown, s)
}
}

View File

@@ -2,41 +2,40 @@ package env
import (
"fmt"
"os"
"strings"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/govalid/binary"
)
func (r *Reader) readOpenVPN() (
openVPN settings.OpenVPN, err error) {
defer func() {
err = unsetEnvKeys([]string{"OPENVPN_CLIENTKEY", "OPENVPN_CLIENTCRT"}, err)
err = unsetEnvKeys([]string{"OPENVPN_KEY", "OPENVPN_CERT",
"OPENVPN_KEY_PASSPHRASE", "OPENVPN_ENCRYPTED_KEY"}, err)
}()
openVPN.Version = os.Getenv("OPENVPN_VERSION")
openVPN.Version = getCleanedEnv("OPENVPN_VERSION")
openVPN.User = r.readOpenVPNUser()
openVPN.Password = r.readOpenVPNPassword()
confFile := os.Getenv("OPENVPN_CUSTOM_CONFIG")
confFile := getCleanedEnv("OPENVPN_CUSTOM_CONFIG")
if confFile != "" {
openVPN.ConfFile = &confFile
}
openVPN.Ciphers = envToCSV("OPENVPN_CIPHER")
auth := os.Getenv("OPENVPN_AUTH")
ciphersKey, _ := r.getEnvWithRetro("OPENVPN_CIPHERS", "OPENVPN_CIPHER")
openVPN.Ciphers = envToCSV(ciphersKey)
auth := getCleanedEnv("OPENVPN_AUTH")
if auth != "" {
openVPN.Auth = &auth
}
openVPN.ClientCrt, err = readBase64OrNil("OPENVPN_CLIENTCRT")
if err != nil {
return openVPN, fmt.Errorf("environment variable OPENVPN_CLIENTCRT: %w", err)
}
openVPN.Cert = envToStringPtr("OPENVPN_CERT")
openVPN.Key = envToStringPtr("OPENVPN_KEY")
openVPN.EncryptedKey = envToStringPtr("OPENVPN_ENCRYPTED_KEY")
openVPN.ClientKey, err = readBase64OrNil("OPENVPN_CLIENTKEY")
if err != nil {
return openVPN, fmt.Errorf("environment variable OPENVPN_CLIENTKEY: %w", err)
}
openVPN.KeyPassphrase = r.readOpenVPNKeyPassphrase()
openVPN.PIAEncPreset = r.readPIAEncryptionPreset()
@@ -50,76 +49,84 @@ func (r *Reader) readOpenVPN() (
return openVPN, fmt.Errorf("environment variable OPENVPN_MSSFIX: %w", err)
}
openVPN.Interface = os.Getenv("OPENVPN_INTERFACE")
_, openVPN.Interface = r.getEnvWithRetro("VPN_INTERFACE", "OPENVPN_INTERFACE")
openVPN.Root, err = envToBoolPtr("OPENVPN_ROOT")
openVPN.ProcessUser, err = r.readOpenVPNProcessUser()
if err != nil {
return openVPN, fmt.Errorf("environment variable OPENVPN_ROOT: %w", err)
return openVPN, err
}
// TODO ProcUser once Root is deprecated.
openVPN.Verbosity, err = envToIntPtr("OPENVPN_VERBOSITY")
if err != nil {
return openVPN, fmt.Errorf("environment variable OPENVPN_VERBOSITY: %w", err)
}
flagsStr := getCleanedEnv("OPENVPN_FLAGS")
if flagsStr != "" {
openVPN.Flags = strings.Fields(flagsStr)
}
return openVPN, nil
}
func (r *Reader) readOpenVPNUser() (user string) {
user = os.Getenv("OPENVPN_USER")
if user == "" {
// Retro-compatibility
user = os.Getenv("USER")
if user != "" {
r.onRetroActive("USER", "OPENVPN_USER")
}
func (r *Reader) readOpenVPNUser() (user *string) {
user = new(string)
_, *user = r.getEnvWithRetro("OPENVPN_USER", "USER")
if *user == "" {
return nil
}
// Remove spaces in user ID to simplify user's life, thanks @JeordyR
return strings.ReplaceAll(user, " ", "")
*user = strings.ReplaceAll(*user, " ", "")
return user
}
func (r *Reader) readOpenVPNPassword() (password string) {
password = os.Getenv("OPENVPN_PASSWORD")
if password != "" {
return password
func (r *Reader) readOpenVPNPassword() (password *string) {
password = new(string)
_, *password = r.getEnvWithRetro("OPENVPN_PASSWORD", "PASSWORD")
if *password == "" {
return nil
}
// Retro-compatibility
password = os.Getenv("PASSWORD")
if password != "" {
r.onRetroActive("PASSWORD", "OPENVPN_PASSWORD")
}
return password
}
func readBase64OrNil(envKey string) (valueOrNil *string, err error) {
value := os.Getenv(envKey)
if value == "" {
return nil, nil //nolint:nilnil
func (r *Reader) readOpenVPNKeyPassphrase() (passphrase *string) {
passphrase = new(string)
*passphrase = getCleanedEnv("OPENVPN_KEY_PASSPHRASE")
if *passphrase == "" {
return nil
}
decoded, err := decodeBase64(value)
if err != nil {
return nil, err
}
return &decoded, nil
return passphrase
}
func (r *Reader) readPIAEncryptionPreset() (presetPtr *string) {
preset := strings.ToLower(os.Getenv("PIA_ENCRYPTION"))
_, preset := r.getEnvWithRetro(
"PRIVATE_INTERNET_ACCESS_OPENVPN_ENCRYPTION_PRESET",
"PIA_ENCRYPTION", "ENCRYPTION")
if preset != "" {
return &preset
}
return nil
}
func (r *Reader) readOpenVPNProcessUser() (processUser string, err error) {
key, value := r.getEnvWithRetro("OPENVPN_PROCESS_USER", "OPENVPN_ROOT")
if key == "OPENVPN_PROCESS_USER" {
return value, nil
}
// Retro-compatibility
preset = strings.ToLower(os.Getenv("ENCRYPTION"))
if preset != "" {
r.onRetroActive("ENCRYPTION", "PIA_ENCRYPTION")
return &preset
if value == "" {
return "", nil
}
return nil
root, err := binary.Validate(value)
if err != nil {
return "", fmt.Errorf("environment variable %s: %w", key, err)
}
if root {
return "root", nil
}
const defaultNonRootUser = "nonrootuser"
return defaultNonRootUser, nil
}

View File

@@ -3,7 +3,6 @@ package env
import (
"errors"
"fmt"
"os"
"strings"
"github.com/qdm12/gluetun/internal/configuration/settings"
@@ -13,7 +12,7 @@ import (
func (r *Reader) readOpenVPNSelection() (
selection settings.OpenVPNSelection, err error) {
confFile := os.Getenv("OPENVPN_CUSTOM_CONFIG")
confFile := getCleanedEnv("OPENVPN_CUSTOM_CONFIG")
if confFile != "" {
selection.ConfFile = &confFile
}
@@ -36,18 +35,9 @@ func (r *Reader) readOpenVPNSelection() (
var ErrOpenVPNProtocolNotValid = errors.New("OpenVPN protocol is not valid")
func (r *Reader) readOpenVPNProtocol() (tcp *bool, err error) {
envKey := "OPENVPN_PROTOCOL"
protocol := strings.ToLower(os.Getenv("OPENVPN_PROTOCOL"))
if protocol == "" {
// Retro-compatibility
protocol = strings.ToLower(os.Getenv("PROTOCOL"))
if protocol != "" {
envKey = "PROTOCOL"
r.onRetroActive("PROTOCOL", "OPENVPN_PROTOCOL")
}
}
envKey, protocol := r.getEnvWithRetro("OPENVPN_PROTOCOL", "PROTOCOL")
switch protocol {
switch strings.ToLower(protocol) {
case "":
return nil, nil //nolint:nilnil
case constants.UDP:
@@ -61,16 +51,9 @@ func (r *Reader) readOpenVPNProtocol() (tcp *bool, err error) {
}
func (r *Reader) readOpenVPNCustomPort() (customPort *uint16, err error) {
key := "OPENVPN_PORT"
s := os.Getenv(key)
key, s := r.getEnvWithRetro("VPN_ENDPOINT_PORT", "PORT", "OPENVPN_PORT")
if s == "" {
// Retro-compatibility
key = "PORT"
s = os.Getenv(key)
if s == "" {
return nil, nil //nolint:nilnil
}
r.onRetroActive("PORT", "OPENVPN_PORT")
return nil, nil //nolint:nilnil
}
customPort = new(uint16)

View File

@@ -6,14 +6,22 @@ import (
"github.com/qdm12/gluetun/internal/configuration/settings"
)
func readPortForward() (
func (r *Reader) readPortForward() (
portForwarding settings.PortForwarding, err error) {
portForwarding.Enabled, err = envToBoolPtr("PORT_FORWARDING")
key, _ := r.getEnvWithRetro(
"PRIVATE_INTERNET_ACCESS_VPN_PORT_FORWARDING",
"PORT_FORWARDING")
portForwarding.Enabled, err = envToBoolPtr(key)
if err != nil {
return portForwarding, fmt.Errorf("environment variable PORT_FORWARDING: %w", err)
return portForwarding, fmt.Errorf("environment variable %s: %w", key, err)
}
portForwarding.Filepath = envToStringPtr("PORT_FORWARDING_STATUS_FILE")
_, value := r.getEnvWithRetro(
"PRIVATE_INTERNET_ACCESS_VPN_PORT_FORWARDING_STATUS_FILE",
"PORT_FORWARDING_STATUS_FILE")
if value != "" {
portForwarding.Filepath = stringPtr(value)
}
return portForwarding, nil
}

View File

@@ -0,0 +1,28 @@
package env
import (
"fmt"
"github.com/qdm12/gluetun/internal/pprof"
)
func readPprof() (settings pprof.Settings, err error) {
settings.Enabled, err = envToBoolPtr("PPROF_ENABLED")
if err != nil {
return settings, fmt.Errorf("environment variable PPROF_ENABLED: %w", err)
}
settings.BlockProfileRate, err = envToInt("PPROF_BLOCK_PROFILE_RATE")
if err != nil {
return settings, fmt.Errorf("environment variable PPROF_BLOCK_PROFILE_RATE: %w", err)
}
settings.MutexProfileRate, err = envToInt("PPROF_MUTEX_PROFILE_RATE")
if err != nil {
return settings, fmt.Errorf("environment variable PPROF_MUTEX_PROFILE_RATE: %w", err)
}
settings.HTTPServer.Address = getCleanedEnv("PPROF_HTTP_SERVER_ADDRESS")
return settings, nil
}

View File

@@ -2,15 +2,15 @@ package env
import (
"fmt"
"os"
"strings"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/constants/vpn"
)
func (r *Reader) readProvider(vpnType string) (provider settings.Provider, err error) {
provider.Name = readVPNServiceProvider(vpnType)
provider.Name = r.readVPNServiceProvider(vpnType)
var providerName string
if provider.Name != nil {
providerName = *provider.Name
@@ -18,27 +18,28 @@ func (r *Reader) readProvider(vpnType string) (provider settings.Provider, err e
provider.ServerSelection, err = r.readServerSelection(providerName, vpnType)
if err != nil {
return provider, fmt.Errorf("cannot read server selection settings: %w", err)
return provider, fmt.Errorf("server selection: %w", err)
}
provider.PortForwarding, err = readPortForward()
provider.PortForwarding, err = r.readPortForward()
if err != nil {
return provider, fmt.Errorf("cannot read port forwarding settings: %w", err)
return provider, fmt.Errorf("port forwarding: %w", err)
}
return provider, nil
}
func readVPNServiceProvider(vpnType string) (vpnProviderPtr *string) {
s := strings.ToLower(os.Getenv("VPNSP"))
func (r *Reader) readVPNServiceProvider(vpnType string) (vpnProviderPtr *string) {
_, s := r.getEnvWithRetro("VPN_SERVICE_PROVIDER", "VPNSP")
s = strings.ToLower(s)
switch {
case vpnType != constants.Wireguard &&
os.Getenv("OPENVPN_CUSTOM_CONFIG") != "": // retro compatibility
return stringPtr(constants.Custom)
case vpnType != vpn.Wireguard &&
getCleanedEnv("OPENVPN_CUSTOM_CONFIG") != "": // retro compatibility
return stringPtr(providers.Custom)
case s == "":
return nil
case s == "pia": // retro compatibility
return stringPtr(constants.PrivateInternetAccess)
return stringPtr(providers.PrivateInternetAccess)
}
return stringPtr(s)
}

View File

@@ -2,7 +2,6 @@ package env
import (
"fmt"
"os"
"time"
"github.com/qdm12/gluetun/internal/configuration/settings"
@@ -20,7 +19,7 @@ func (r *Reader) readPublicIP() (publicIP settings.PublicIP, err error) {
}
func readPublicIPPeriod() (period *time.Duration, err error) {
s := os.Getenv("PUBLICIP_PERIOD")
s := getCleanedEnv("PUBLICIP_PERIOD")
if s == "" {
return nil, nil //nolint:nilnil
}
@@ -35,17 +34,9 @@ func readPublicIPPeriod() (period *time.Duration, err error) {
}
func (r *Reader) readPublicIPFilepath() (filepath *string) {
s := os.Getenv("PUBLICIP_FILE")
_, s := r.getEnvWithRetro("PUBLICIP_FILE", "IP_STATUS_FILE")
if s != "" {
return &s
}
// Retro-compatibility
s = os.Getenv("IP_STATUS_FILE")
if s != "" {
r.onRetroActive("IP_STATUS_FILE", "PUBLICIP_FILE")
return &s
}
return nil
}

View File

@@ -21,6 +21,8 @@ func New(warner Warner) *Reader {
}
}
func (r *Reader) String() string { return "environment variables" }
func (r *Reader) Read() (settings settings.Settings, err error) {
settings.VPN, err = r.readVPN()
if err != nil {
@@ -77,7 +79,12 @@ func (r *Reader) Read() (settings settings.Settings, err error) {
return settings, err
}
settings.ControlServer, err = readControlServer()
settings.ControlServer, err = r.readControlServer()
if err != nil {
return settings, err
}
settings.Pprof, err = readPprof()
if err != nil {
return settings, err
}
@@ -90,3 +97,24 @@ func (r *Reader) onRetroActive(oldKey, newKey string) {
"You are using the old environment variable " + oldKey +
", please consider changing it to " + newKey)
}
// getEnvWithRetro returns the first environment variable
// key and corresponding non empty value from the environment
// variable keys given. It first goes through the retroKeys
// and end on returning the value corresponding to the currentKey.
// Note retroKeys should be in order from oldest to most
// recent retro-compatibility key.
func (r *Reader) getEnvWithRetro(currentKey string,
retroKeys ...string) (key, value string) {
// We check retro-compatibility keys first since
// the current key might be set in the Dockerfile.
for _, key = range retroKeys {
value = getCleanedEnv(key)
if value != "" {
r.onRetroActive(key, currentKey)
return key, value
}
}
return currentKey, getCleanedEnv(currentKey)
}

View File

@@ -2,29 +2,24 @@ package env
import (
"fmt"
"os"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/govalid/binary"
"github.com/qdm12/govalid/port"
)
func readControlServer() (controlServer settings.ControlServer, err error) {
func (r *Reader) readControlServer() (controlServer settings.ControlServer, err error) {
controlServer.Log, err = readControlServerLog()
if err != nil {
return controlServer, err
}
controlServer.Port, err = readControlServerPort()
if err != nil {
return controlServer, err
}
controlServer.Address = r.readControlServerAddress()
return controlServer, nil
}
func readControlServerLog() (enabled *bool, err error) {
s := os.Getenv("HTTP_CONTROL_SERVER_LOG")
s := getCleanedEnv("HTTP_CONTROL_SERVER_LOG")
if s == "" {
return nil, nil //nolint:nilnil
}
@@ -37,17 +32,17 @@ func readControlServerLog() (enabled *bool, err error) {
return &log, nil
}
func readControlServerPort() (p *uint16, err error) {
s := os.Getenv("HTTP_CONTROL_SERVER_PORT")
func (r *Reader) readControlServerAddress() (address *string) {
key, s := r.getEnvWithRetro("HTTP_CONTROL_SERVER_ADDRESS", "HTTP_CONTROL_SERVER_PORT")
if s == "" {
return nil, nil //nolint:nilnil
return nil
}
p = new(uint16)
*p, err = port.Validate(s, port.OptionPortListening(os.Geteuid()))
if err != nil {
return nil, fmt.Errorf("environment variable HTTP_CONTROL_SERVER_PORT: %w", err)
if key == "HTTP_CONTROL_SERVER_ADDRESS" {
return &s
}
return p, nil
address = new(string)
*address = ":" + s
return address
}

View File

@@ -4,12 +4,11 @@ import (
"errors"
"fmt"
"net"
"os"
"strconv"
"strings"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/constants/providers"
)
var (
@@ -20,28 +19,36 @@ func (r *Reader) readServerSelection(vpnProvider, vpnType string) (
ss settings.ServerSelection, err error) {
ss.VPN = vpnType
ss.TargetIP, err = readOpenVPNTargetIP()
ss.TargetIP, err = r.readOpenVPNTargetIP()
if err != nil {
return ss, err
}
countriesCSV := os.Getenv("COUNTRY")
if vpnProvider == constants.Cyberghost && countriesCSV == "" {
// Retro-compatibility
r.onRetroActive("REGION", "COUNTRY")
countriesCSV = os.Getenv("REGION")
}
if countriesCSV != "" {
ss.Countries = lowerAndSplit(countriesCSV)
countriesKey, _ := r.getEnvWithRetro("SERVER_COUNTRIES", "COUNTRY")
ss.Countries = envToCSV(countriesKey)
if vpnProvider == providers.Cyberghost && len(ss.Countries) == 0 {
// Retro-compatibility for Cyberghost using the REGION variable
ss.Countries = envToCSV("REGION")
if len(ss.Countries) > 0 {
r.onRetroActive("REGION", "SERVER_COUNTRIES")
}
}
ss.Regions = envToCSV("REGION")
ss.Cities = envToCSV("CITY")
regionsKey, _ := r.getEnvWithRetro("SERVER_REGIONS", "REGION")
ss.Regions = envToCSV(regionsKey)
citiesKey, _ := r.getEnvWithRetro("SERVER_CITIES", "CITY")
ss.Cities = envToCSV(citiesKey)
ss.ISPs = envToCSV("ISP")
ss.Hostnames = envToCSV("SERVER_HOSTNAME")
ss.Names = envToCSV("SERVER_NAME")
if csv := os.Getenv("SERVER_NUMBER"); csv != "" {
hostnamesKey, _ := r.getEnvWithRetro("SERVER_HOSTNAMES", "SERVER_HOSTNAME")
ss.Hostnames = envToCSV(hostnamesKey)
serverNamesKey, _ := r.getEnvWithRetro("SERVER_NAMES", "SERVER_NAME")
ss.Names = envToCSV(serverNamesKey)
if csv := getCleanedEnv("SERVER_NUMBER"); csv != "" {
numbersStrings := strings.Split(csv, ",")
numbers := make([]uint16, len(numbersStrings))
for i, numberString := range numbersStrings {
@@ -59,9 +66,9 @@ func (r *Reader) readServerSelection(vpnProvider, vpnType string) (
}
// Mullvad only
ss.OwnedOnly, err = envToBoolPtr("OWNED")
ss.OwnedOnly, err = r.readOwnedOnly()
if err != nil {
return ss, fmt.Errorf("environment variable OWNED: %w", err)
return ss, err
}
// VPNUnlimited and ProtonVPN only
@@ -70,6 +77,12 @@ func (r *Reader) readServerSelection(vpnProvider, vpnType string) (
return ss, fmt.Errorf("environment variable FREE_ONLY: %w", err)
}
// VPNSecure only
ss.PremiumOnly, err = envToBoolPtr("PREMIUM_ONLY")
if err != nil {
return ss, fmt.Errorf("environment variable PREMIUM_ONLY: %w", err)
}
// VPNUnlimited only
ss.MultiHopOnly, err = envToBoolPtr("MULTIHOP_ONLY")
if err != nil {
@@ -99,17 +112,26 @@ var (
ErrInvalidIP = errors.New("invalid IP address")
)
func readOpenVPNTargetIP() (ip net.IP, err error) {
s := os.Getenv("OPENVPN_TARGET_IP")
func (r *Reader) readOpenVPNTargetIP() (ip net.IP, err error) {
envKey, s := r.getEnvWithRetro("VPN_ENDPOINT_IP", "OPENVPN_TARGET_IP")
if s == "" {
return nil, nil
}
ip = net.ParseIP(s)
if ip == nil {
return nil, fmt.Errorf("environment variable OPENVPN_TARGET_IP: %w: %s",
ErrInvalidIP, s)
return nil, fmt.Errorf("environment variable %s: %w: %s",
envKey, ErrInvalidIP, s)
}
return ip, nil
}
func (r *Reader) readOwnedOnly() (ownedOnly *bool, err error) {
envKey, _ := r.getEnvWithRetro("OWNED_ONLY", "OWNED")
ownedOnly, err = envToBoolPtr(envKey)
if err != nil {
return nil, fmt.Errorf("environment variable %s: %w", envKey, err)
}
return ownedOnly, nil
}

View File

@@ -2,7 +2,7 @@ package env
import (
"fmt"
"os"
"strings"
"github.com/qdm12/gluetun/internal/configuration/settings"
)
@@ -25,30 +25,20 @@ func (r *Reader) readShadowsocks() (shadowsocks settings.Shadowsocks, err error)
}
func (r *Reader) readShadowsocksAddress() (address string) {
address = os.Getenv("SHADOWSOCKS_LISTENING_ADDRESS")
if address != "" {
return address
key, value := r.getEnvWithRetro("SHADOWSOCKS_LISTENING_ADDRESS", "SHADOWSOCKS_PORT")
if value == "" {
return ""
}
if key == "SHADOWSOCKS_LISTENING_ADDRESS" {
return value
}
// Retro-compatibility
portString := os.Getenv("SHADOWSOCKS_PORT")
if portString != "" {
r.onRetroActive("SHADOWSOCKS_PORT", "SHADOWSOCKS_LISTENING_ADDRESS")
return ":" + portString
}
return ""
return ":" + value
}
func (r *Reader) readShadowsocksCipher() (cipher string) {
cipher = os.Getenv("SHADOWSOCKS_CIPHER")
if cipher != "" {
return cipher
}
// Retro-compatibility
cipher = os.Getenv("SHADOWSOCKS_METHOD")
if cipher != "" {
r.onRetroActive("SHADOWSOCKS_METHOD", "SHADOWSOCKS_CIPHER")
}
return cipher
_, cipher = r.getEnvWithRetro("SHADOWSOCKS_CIPHER", "SHADOWSOCKS_METHOD")
return strings.ToLower(cipher)
}

View File

@@ -3,7 +3,6 @@ package env
import (
"errors"
"fmt"
"os"
"strconv"
"github.com/qdm12/gluetun/internal/configuration/settings"
@@ -26,7 +25,7 @@ func (r *Reader) readSystem() (system settings.System, err error) {
return system, err
}
system.Timezone = os.Getenv("TZ")
system.Timezone = getCleanedEnv("TZ")
return system, nil
}
@@ -34,30 +33,23 @@ func (r *Reader) readSystem() (system settings.System, err error) {
var ErrSystemIDNotValid = errors.New("system ID is not valid")
func (r *Reader) readID(key, retroKey string) (
id *uint16, err error) {
idEnvKey := key
idString := os.Getenv(key)
if idString == "" {
// retro-compatibility
idString = os.Getenv(retroKey)
if idString != "" {
idEnvKey = retroKey
r.onRetroActive(retroKey, key)
}
}
id *uint32, err error) {
idEnvKey, idString := r.getEnvWithRetro(key, retroKey)
if idString == "" {
return nil, nil //nolint:nilnil
}
idInt, err := strconv.Atoi(idString)
const base = 10
const bitSize = 64
const max = uint64(^uint32(0))
idUint64, err := strconv.ParseUint(idString, base, bitSize)
if err != nil {
return nil, fmt.Errorf("environment variable %s: %w: %s: %s",
idEnvKey, ErrSystemIDNotValid, idString, err)
} else if idInt < 0 || idInt > 65535 {
return nil, fmt.Errorf("environment variable %s: %w: %d: must be between 0 and 65535",
idEnvKey, ErrSystemIDNotValid, idInt)
return nil, fmt.Errorf("environment variable %s: %w: %s",
idEnvKey, ErrSystemIDNotValid, err)
} else if idUint64 > max {
return nil, fmt.Errorf("environment variable %s: %w: %d: must be between 0 and %d",
idEnvKey, ErrSystemIDNotValid, idUint64, max)
}
return uint16Ptr(uint16(idInt)), nil
return uint32Ptr(uint32(idUint64)), nil
}

View File

@@ -14,15 +14,51 @@ func Test_Reader_readID(t *testing.T) {
keyValue string
retroKeyPrefix string
retroValue string
id *uint16
id *uint32
errWrapped error
errMessage string
}{
"empty string": {
keyPrefix: "ID",
retroKeyPrefix: "RETRO_ID",
},
"invalid string": {
keyPrefix: "ID",
keyValue: "invalid",
retroKeyPrefix: "RETRO_ID",
errWrapped: ErrSystemIDNotValid,
errMessage: `environment variable IDTest_Reader_readID/invalid_string: ` +
`system ID is not valid: ` +
`strconv.ParseUint: parsing "invalid": invalid syntax`,
},
"negative number": {
keyPrefix: "ID",
keyValue: "-1",
retroKeyPrefix: "RETRO_ID",
errWrapped: ErrSystemIDNotValid,
errMessage: `environment variable IDTest_Reader_readID/negative_number: ` +
`system ID is not valid: ` +
`strconv.ParseUint: parsing "-1": invalid syntax`,
},
"id 1000": {
keyPrefix: "ID",
keyValue: "1000",
retroKeyPrefix: "RETRO_ID",
id: uint16Ptr(1000),
id: uint32Ptr(1000),
},
"max id": {
keyPrefix: "ID",
keyValue: "4294967295",
retroKeyPrefix: "RETRO_ID",
id: uint32Ptr(4294967295),
},
"above max id": {
keyPrefix: "ID",
keyValue: "4294967296",
retroKeyPrefix: "RETRO_ID",
errWrapped: ErrSystemIDNotValid,
errMessage: `environment variable IDTest_Reader_readID/above_max_id: ` +
`system ID is not valid: 4294967296: must be between 0 and 4294967295`,
},
}

View File

@@ -2,12 +2,9 @@ package env
import (
"fmt"
"net"
"os"
"time"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/constants"
)
func readUpdater() (updater settings.Updater, err error) {
@@ -21,19 +18,18 @@ func readUpdater() (updater settings.Updater, err error) {
return updater, err
}
// TODO use current provider being used
for _, provider := range constants.AllProviders() {
if provider == constants.Custom {
continue
}
updater.Providers = append(updater.Providers, provider)
updater.MinRatio, err = envToFloat64("UPDATER_MIN_RATIO")
if err != nil {
return updater, fmt.Errorf("environment variable UPDATER_MIN_RATIO: %w", err)
}
updater.Providers = envToCSV("UPDATER_VPN_SERVICE_PROVIDERS")
return updater, nil
}
func readUpdaterPeriod() (period *time.Duration, err error) {
s := os.Getenv("UPDATER_PERIOD")
s := getCleanedEnv("UPDATER_PERIOD")
if s == "" {
return nil, nil //nolint:nilnil
}
@@ -45,11 +41,11 @@ func readUpdaterPeriod() (period *time.Duration, err error) {
return period, nil
}
func readUpdaterDNSAddress() (ip net.IP, err error) {
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, nil
return "", nil
}

View File

@@ -2,7 +2,6 @@ package env
import (
"fmt"
"os"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/govalid/binary"
@@ -18,7 +17,7 @@ func readVersion() (version settings.Version, err error) {
}
func readVersionEnabled() (enabled *bool, err error) {
s := os.Getenv("VERSION_INFORMATION")
s := getCleanedEnv("VERSION_INFORMATION")
if s == "" {
return nil, nil //nolint:nilnil
}

View File

@@ -2,28 +2,27 @@ package env
import (
"fmt"
"os"
"strings"
"github.com/qdm12/gluetun/internal/configuration/settings"
)
func (r *Reader) readVPN() (vpn settings.VPN, err error) {
vpn.Type = strings.ToLower(os.Getenv("VPN_TYPE"))
vpn.Type = strings.ToLower(getCleanedEnv("VPN_TYPE"))
vpn.Provider, err = r.readProvider(vpn.Type)
if err != nil {
return vpn, fmt.Errorf("cannot read provider settings: %w", err)
return vpn, fmt.Errorf("VPN provider: %w", err)
}
vpn.OpenVPN, err = r.readOpenVPN()
if err != nil {
return vpn, fmt.Errorf("cannot read OpenVPN settings: %w", err)
return vpn, fmt.Errorf("OpenVPN: %w", err)
}
vpn.Wireguard, err = readWireguard()
vpn.Wireguard, err = r.readWireguard()
if err != nil {
return vpn, fmt.Errorf("cannot read Wireguard settings: %w", err)
return vpn, fmt.Errorf("wireguard: %w", err)
}
return vpn, nil

View File

@@ -3,28 +3,27 @@ package env
import (
"fmt"
"net"
"os"
"strings"
"github.com/qdm12/gluetun/internal/configuration/settings"
)
func readWireguard() (wireguard settings.Wireguard, err error) {
func (r *Reader) readWireguard() (wireguard settings.Wireguard, err error) {
defer func() {
err = unsetEnvKeys([]string{"WIREGUARD_PRIVATE_KEY", "WIREGUARD_PRESHARED_KEY"}, err)
}()
wireguard.PrivateKey = envToStringPtr("WIREGUARD_PRIVATE_KEY")
wireguard.PreSharedKey = envToStringPtr("WIREGUARD_PRESHARED_KEY")
wireguard.Interface = os.Getenv("WIREGUARD_INTERFACE")
wireguard.Addresses, err = readWireguardAddresses()
_, wireguard.Interface = r.getEnvWithRetro("VPN_INTERFACE", "WIREGUARD_INTERFACE")
wireguard.Addresses, err = r.readWireguardAddresses()
if err != nil {
return wireguard, err // already wrapped
}
return wireguard, nil
}
func readWireguardAddresses() (addresses []net.IPNet, err error) {
addressesCSV := os.Getenv("WIREGUARD_ADDRESS")
func (r *Reader) readWireguardAddresses() (addresses []net.IPNet, err error) {
key, addressesCSV := r.getEnvWithRetro("WIREGUARD_ADDRESSES", "WIREGUARD_ADDRESS")
if addressesCSV == "" {
return nil, nil
}
@@ -34,7 +33,7 @@ func readWireguardAddresses() (addresses []net.IPNet, err error) {
for i, addressString := range addressStrings {
ip, ipNet, err := net.ParseCIDR(addressString)
if err != nil {
return nil, fmt.Errorf("environment variable WIREGUARD_ADDRESS: %w", err)
return nil, fmt.Errorf("environment variable %s: %w", key, err)
}
ipNet.IP = ip
addresses[i] = *ipNet

View File

@@ -4,7 +4,6 @@ import (
"errors"
"fmt"
"net"
"os"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/govalid/port"
@@ -12,7 +11,7 @@ import (
func (r *Reader) readWireguardSelection() (
selection settings.WireguardSelection, err error) {
selection.EndpointIP, err = readWireguardEndpointIP()
selection.EndpointIP, err = r.readWireguardEndpointIP()
if err != nil {
return selection, err
}
@@ -22,37 +21,32 @@ func (r *Reader) readWireguardSelection() (
return selection, err
}
selection.PublicKey = os.Getenv("WIREGUARD_PUBLIC_KEY")
selection.PublicKey = getCleanedEnv("WIREGUARD_PUBLIC_KEY")
return selection, nil
}
var ErrIPAddressParse = errors.New("cannot parse IP address")
func readWireguardEndpointIP() (endpointIP net.IP, err error) {
s := os.Getenv("WIREGUARD_ENDPOINT_IP")
func (r *Reader) readWireguardEndpointIP() (endpointIP net.IP, err error) {
key, s := r.getEnvWithRetro("VPN_ENDPOINT_IP", "WIREGUARD_ENDPOINT_IP")
if s == "" {
return nil, nil
}
endpointIP = net.ParseIP(s)
if endpointIP == nil {
return nil, fmt.Errorf("environment variable WIREGUARD_ENDPOINT_IP: %w: %s",
ErrIPAddressParse, s)
return nil, fmt.Errorf("environment variable %s: %w: %s",
key, ErrIPAddressParse, s)
}
return endpointIP, nil
}
func (r *Reader) readWireguardCustomPort() (customPort *uint16, err error) {
key := "WIREGUARD_ENDPOINT_PORT"
s := os.Getenv(key)
key, s := r.getEnvWithRetro("VPN_ENDPOINT_PORT", "WIREGUARD_ENDPOINT_PORT")
if s == "" {
// Retro-compatibility
key = "WIREGUARD_PORT"
s = os.Getenv(key)
if s == "" {
return nil, nil //nolint:nilnil
}
r.onRetroActive("WIREGUARD_PORT", "WIREGUARD_ENDPOINT_PORT")
return nil, nil //nolint:nilnil
}
customPort = new(uint16)

View File

@@ -1,9 +1,12 @@
package files
import (
"fmt"
"io"
"os"
"strings"
"github.com/qdm12/gluetun/internal/openvpn/extract"
)
// ReadFromFile reads the content of the file as a string.
@@ -32,3 +35,21 @@ func ReadFromFile(filepath string) (s *string, err error) {
content = strings.TrimSuffix(content, "\n")
return &content, nil
}
func readPEMFile(filepath string) (base64Ptr *string, err error) {
pemData, err := ReadFromFile(filepath)
if err != nil {
return nil, fmt.Errorf("reading 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
}

View File

@@ -4,18 +4,29 @@ import (
"fmt"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/constants"
)
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 (r *Reader) readOpenVPN() (settings settings.OpenVPN, err error) {
settings.ClientKey, err = ReadFromFile(constants.ClientKey)
settings.Key, err = readPEMFile(OpenVPNClientKeyPath)
if err != nil {
return settings, fmt.Errorf("cannot read client key: %w", err)
return settings, fmt.Errorf("client key: %w", err)
}
settings.ClientCrt, err = ReadFromFile(constants.ClientCertificate)
settings.Cert, err = readPEMFile(OpenVPNClientCertificatePath)
if err != nil {
return settings, fmt.Errorf("cannot read client certificate: %w", err)
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

View File

@@ -13,6 +13,8 @@ func New() *Reader {
return &Reader{}
}
func (r *Reader) String() string { return "files" }
func (r *Reader) Read() (settings settings.Settings, err error) {
settings.VPN, err = r.readVPN()
if err != nil {

View File

@@ -9,7 +9,7 @@ import (
func (r *Reader) readVPN() (vpn settings.VPN, err error) {
vpn.OpenVPN, err = r.readOpenVPN()
if err != nil {
return vpn, fmt.Errorf("cannot read OpenVPN settings: %w", err)
return vpn, fmt.Errorf("OpenVPN: %w", err)
}
return vpn, nil

View File

@@ -2,6 +2,7 @@ package mux
import (
"fmt"
"strings"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/configuration/sources"
@@ -19,6 +20,14 @@ func New(sources ...sources.Source) *Reader {
}
}
func (r *Reader) String() string {
sources := make([]string, len(r.sources))
for i := range r.sources {
sources[i] = r.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.
@@ -26,7 +35,7 @@ func (r *Reader) Read() (settings settings.Settings, err error) {
for _, source := range r.sources {
settingsFromSource, err := source.Read()
if err != nil {
return settings, fmt.Errorf("cannot read from source %T: %w", source, err)
return settings, fmt.Errorf("reading from %s: %w", source, err)
}
settings.MergeWith(settingsFromSource)
}
@@ -42,7 +51,7 @@ func (r *Reader) ReadHealth() (settings settings.Health, err error) {
for _, source := range r.sources {
settingsFromSource, err := source.ReadHealth()
if err != nil {
return settings, fmt.Errorf("cannot read from source %T: %w", source, err)
return settings, fmt.Errorf("reading from %s: %w", source, err)
}
settings.MergeWith(settingsFromSource)
}

View File

@@ -1,31 +1,48 @@
package secrets
import (
"fmt"
"os"
"strings"
"github.com/qdm12/gluetun/internal/configuration/sources/files"
"github.com/qdm12/gluetun/internal/openvpn/extract"
)
// getCleanedEnv returns an environment variable value with
// surrounding spaces and trailing new line characters removed.
func getCleanedEnv(envKey string) (value string) {
value = os.Getenv(envKey)
value = strings.TrimSpace(value)
value = strings.TrimSuffix(value, "\r\n")
value = strings.TrimSuffix(value, "\n")
return value
}
func readSecretFileAsStringPtr(secretPathEnvKey, defaultSecretPath string) (
stringPtr *string, err error) {
path := os.Getenv(secretPathEnvKey)
path := getCleanedEnv(secretPathEnvKey)
if path == "" {
path = defaultSecretPath
}
return files.ReadFromFile(path)
}
func readSecretFileAsString(secretPathEnvKey, defaultSecretPath string) (
s string, err error) {
path := os.Getenv(secretPathEnvKey)
if path == "" {
path = defaultSecretPath
}
stringPtr, err := files.ReadFromFile(path)
func readPEMSecretFile(secretPathEnvKey, defaultSecretPath string) (
base64Ptr *string, err error) {
pemData, err := readSecretFileAsStringPtr(secretPathEnvKey, defaultSecretPath)
if err != nil {
return "", err
} else if stringPtr == nil {
return "", nil
return nil, fmt.Errorf("reading secret file: %w", err)
}
return *stringPtr, nil
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
}

View File

@@ -8,7 +8,7 @@ import (
func readOpenVPN() (
settings settings.OpenVPN, err error) {
settings.User, err = readSecretFileAsString(
settings.User, err = readSecretFileAsStringPtr(
"OPENVPN_USER_SECRETFILE",
"/run/secrets/openvpn_user",
)
@@ -16,7 +16,7 @@ func readOpenVPN() (
return settings, fmt.Errorf("cannot read user file: %w", err)
}
settings.Password, err = readSecretFileAsString(
settings.Password, err = readSecretFileAsStringPtr(
"OPENVPN_PASSWORD_SECRETFILE",
"/run/secrets/openvpn_password",
)
@@ -24,7 +24,7 @@ func readOpenVPN() (
return settings, fmt.Errorf("cannot read password file: %w", err)
}
settings.ClientKey, err = readSecretFileAsStringPtr(
settings.Key, err = readPEMSecretFile(
"OPENVPN_CLIENTKEY_SECRETFILE",
"/run/secrets/openvpn_clientkey",
)
@@ -32,7 +32,23 @@ func readOpenVPN() (
return settings, fmt.Errorf("cannot read client key file: %w", err)
}
settings.ClientCrt, err = readSecretFileAsStringPtr(
settings.EncryptedKey, err = 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 = 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 = readPEMSecretFile(
"OPENVPN_CLIENTCRT_SECRETFILE",
"/run/secrets/openvpn_clientcrt",
)

View File

@@ -14,6 +14,8 @@ func New() *Reader {
return &Reader{}
}
func (r *Reader) String() string { return "secret files" }
func (r *Reader) Read() (settings settings.Settings, err error) {
settings.VPN, err = readVPN()
if err != nil {

View File

@@ -5,4 +5,5 @@ import "github.com/qdm12/gluetun/internal/configuration/settings"
type Source interface {
Read() (settings settings.Settings, err error)
ReadHealth() (settings settings.Health, err error)
String() string
}

View File

@@ -1,25 +0,0 @@
// Package constants defines constants shared throughout the program.
// It also defines constant maps and slices using functions.
package constants
import "sort"
func makeChoicesUnique(choices []string) []string {
uniqueChoices := map[string]struct{}{}
for _, choice := range choices {
uniqueChoices[choice] = struct{}{}
}
uniqueChoicesSlice := make([]string, len(uniqueChoices))
i := 0
for choice := range uniqueChoices {
uniqueChoicesSlice[i] = choice
i++
}
sort.Slice(uniqueChoicesSlice, func(i, j int) bool {
return uniqueChoicesSlice[i] < uniqueChoicesSlice[j]
})
return uniqueChoicesSlice
}

View File

@@ -1,26 +0,0 @@
package constants
import (
"github.com/qdm12/gluetun/internal/models"
)
//nolint:lll
const (
CyberghostCertificate = "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"
)
func CyberghostCountryChoices(servers []models.CyberghostServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Country
}
return makeUnique(choices)
}
func CyberghostHostnameChoices(servers []models.CyberghostServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Hostname
}
return makeUnique(choices)
}

View File

@@ -1,37 +0,0 @@
package constants
import (
"github.com/qdm12/gluetun/internal/models"
)
//nolint:lll
const (
ExpressvpnCert = "MIIDTjCCAregAwIBAgIDKzZvMA0GCSqGSIb3DQEBCwUAMIGFMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFTATBgNVBAcTDFNhbkZyYW5jaXNjbzEVMBMGA1UEChMMRm9ydC1GdW5zdG9uMRgwFgYDVQQDEw9Gb3J0LUZ1bnN0b24gQ0ExITAfBgkqhkiG9w0BCQEWEm1lQG15aG9zdC5teWRvbWFpbjAgFw0xNjExMDMwMzA2MThaGA8yMDY2MTEwMzAzMDYxOFowgYoxCzAJBgNVBAYTAlZHMQwwCgYDVQQIDANCVkkxEzARBgNVBAoMCkV4cHJlc3NWUE4xEzARBgNVBAsMCkV4cHJlc3NWUE4xHDAaBgNVBAMME2V4cHJlc3N2cG5fY3VzdG9tZXIxJTAjBgkqhkiG9w0BCQEWFnN1cHBvcnRAZXhwcmVzc3Zwbi5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCrOYt/KOi2uMDGev3pXg8j1SO4J/4EVWDF7vJcKr2jrZlqD/zuAFx2W1YWvwumPO6PKH4PU9621aNdiumaUkv/RplCfznnnxqobhJuTE2oA+rS1bOq+9OhHwF9jgNXNVk+XX4d0toST5uGE6Z3OdmPBur8o5AlCf78PDSAwpFOw5HrgLqOEU4hTweC1/czX2VsvsHv22HRI6JMZgP8gGQii/p9iukqfaJvGdPciL5p1QRBUQIi8P8pNvEp1pVIpxYj7/LOUqb2DxFvgmp2v1IQ0Yu88SWsFk84+xAYHzfkLyS31Sqj5uLRBnJqx3fIlOihQ50GI72fwPMwo+OippvVAgMBAAGjPzA9MAwGA1UdEwEB/wQCMAAwDgYDVR0PAQH/BAQDAgSwMB0GA1UdDgQWBBSkBM1TCX9kBgFsv2RmOzudMXa9njANBgkqhkiG9w0BAQsFAAOBgQA+2e4b+33zFmA+1ZQ46kWkfiB+fEeDyMwMLeYYyDS2d8mZhNZKdOw7dy4Ifz9Vqzp4aKuQ6j61c6k1UaQQL0tskqWVzslSFvs9NZyUAJLLdGUc5TT2MiLwiXQwd4UvH6bGeePdhvB4+ZbW7VMD7TE8hZhjhAL4F6yAP1EQvg3LDA=="
ExpressvpnRSAKey = "MIIEpAIBAAKCAQEAqzmLfyjotrjAxnr96V4PI9UjuCf+BFVgxe7yXCq9o62Zag/87gBcdltWFr8Lpjzujyh+D1PettWjXYrpmlJL/0aZQn85558aqG4SbkxNqAPq0tWzqvvToR8BfY4DVzVZPl1+HdLaEk+bhhOmdznZjwbq/KOQJQn+/Dw0gMKRTsOR64C6jhFOIU8Hgtf3M19lbL7B79th0SOiTGYD/IBkIov6fYrpKn2ibxnT3Ii+adUEQVECIvD/KTbxKdaVSKcWI+/yzlKm9g8Rb4Jqdr9SENGLvPElrBZPOPsQGB835C8kt9Uqo+bi0QZyasd3yJTooUOdBiO9n8DzMKPjoqab1QIDAQABAoIBAHgsekC0SKi+AOcNOZqJ3pxqophE0V7fQX2KWGXhxZnUZMFxGTc936deMYzjZ1y0lUa6x8cgOUcfqHol3hDmw9oWBckLHGv5Wi9umdb6DOLoZO62+FQATSdfaJ9jheq2Ub2YxsRN0apaXzB6KDKz0oM0+sZ4Udn9Kw6DfuIELRIWwEx4w0v3gKW7YLC4Jkc4AwLkPK03xEA/qImfkCmaMPLhrgVQt+IFfP8bXzL7CCC04rNU/IS8pyjex+iUolnQZlbXntF7Bm4V2mz0827ZVqrgAb/hEQRlsTW3rRkVh+rrdoUE7BCZRTFmRCbLoShjN6XuSf4sAus8ch4UEN12gN0CgYEA4o/tSvij1iPaXLmt4KOEuxqmSGB8MLKhFde8lBbNdrDgxiIH9bH7khKx15XRTX0qLDbs8b2/UJygZG0Aa1kIBqZTXTgeMAuxPRTesALJPdqQ/ROnbJcdFkI7gllrAG8VB0fH4wTRsRd0vWEB6YlCdE107u6LEsLAHxOj9Q5819cCgYEAwXjx9RkQ2qITBx5Ewib8YsltA0n3cmRomPicLlsnKV5DfvyCLpFIsZ1h3f9dUpfxRLwzp8wcoLiq9cCoOGdu1udw/yBTqmhaXWhUK/g77f9Ze2ZB1OEhuyKLYJ1vW/h/Z/a1aPCMxZqsDTPCePsuO8Cez5gqs8LjM3W7EyzRxDMCgYEAvhHrDFt975fSiLoJgo0MPIAGAnBXn+8sLwv3m/FpW+rWF8LTFK/Fku12H5wDpNOdvswxijkauIE+GiJMGMLvdcyx4WHECaC1h73reJRNykOEIZ0Md5BrCZJ1JEzp9Mo8RQhWTEFtvfkkqgApP4g0pSeaMx0StaGG1kt+4IbP+68CgYBrZdQKlquAck/Vt7u7eyDHRcE5/ilaWtqlb/xizz7h++3D5C/v4b5UumTFcyg+3RGVclPKZcfOgDSGzzeSd/hTW46iUTOgeOUQzQVMkzPRXdoyYgVRQtgSpY5xR3O1vjAbahwx8LZ0SvQPMBhYSDbV/Isr+fBacWjl/AipEEwxeQKBgQDdrAEnVlOFoCLw4sUjsPoxkLjhTAgI7CYk5NNxX67Rnj0tp+Y49+sGUhl5sCGfMKkLShiON5P2oxZa+B0aPtQjsdnsFPa1uaZkK4c++SS6AetzYRpVDLmLp7/1CulE0z3O0sBekpwiuaqLJ9ZccC81g4+2j8j6c50rIAct3hxIxw=="
ExpressvpnTLSAuthOpenvpnStaticKeyV1 = "48d9999bd71095b10649c7cb471c1051b1afdece597cea06909b99303a18c67401597b12c04a787e98cdb619ee960d90a0165529dc650f3a5c6fbe77c91c137dcf55d863fcbe314df5f0b45dbe974d9bde33ef5b4803c3985531c6c23ca6906d6cd028efc8585d1b9e71003566bd7891b9cc9212bcba510109922eed87f5c8e66d8e59cbd82575261f02777372b2cd4ca5214c4a6513ff26dd568f574fd40d6cd450fc788160ff68434ce2bf6afb00e710a3198538f14c4d45d84ab42637872e778a6b35a124e700920879f1d003ba93dccdb953cdf32bea03f365760b0ed8002098d4ce20d045b45a83a8432cc737677aed27125592a7148d25c87fdbe0a3f6"
ExpressvpnCA = "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"
)
func ExpressvpnCountriesChoices(servers []models.ExpressvpnServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Country
}
return makeUnique(choices)
}
func ExpressvpnCityChoices(servers []models.ExpressvpnServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].City
}
return makeUnique(choices)
}
func ExpressvpnHostnameChoices(servers []models.ExpressvpnServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Hostname
}
return makeUnique(choices)
}

View File

@@ -1,27 +0,0 @@
package constants
import (
"github.com/qdm12/gluetun/internal/models"
)
//nolint:lll
const (
FastestvpnCertificate = "MIIFQjCCAyqgAwIBAgIIUfxepT+rr8owDQYJKoZIhvcNAQEMBQAwPzELMAkGA1UEBhMCS1kxEzARBgNVBAoTCkZhc3Rlc3RWUE4xGzAZBgNVBAMTEkZhc3Rlc3RWUE4gUm9vdCBDQTAeFw0xNzA5MTYwMDAxNDZaFw0yNzA5MTQwMDAxNDZaMD8xCzAJBgNVBAYTAktZMRMwEQYDVQQKEwpGYXN0ZXN0VlBOMRswGQYDVQQDExJGYXN0ZXN0VlBOIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC1Xj+WfPTozFynFqc+c3CVrggIllaXEl5bY5VgFynXkqCTM6lSrfC4pNjGXUbqWe6RnGJbM4/6kUn+lQDjFSQV1rzP2eDS8+r5+X2WXh4AoeNRUWhvSG+HiHD/B2EFK+Nd5BRSdUjpKWAtsCmT2bBt7nT0jN1OdeNrLJeyF8siAqv/oQzKznF9aIe/N01b2M8ZOFTzoXi2fZAckgGWui8NB/lzkVIJqSkAPRL8qiJLuRCPVOX1PFD8vV//R8/QumtfbcYBMo6vCk2HmWdrh5OQHPxb3KJtbtG+Z1j8x6HGEAe17djYepBiRMyCEQvYgfD6tvFylc4IquhqE9yaP60PJod5TxpWnRQ6HIGSeBm+S+rYSMalTZ8+pUqOOA+IQCYpfpx6EKIJL/VsW2C7cXdvudxDhXPI5lR/QidCb9Ohq3WkfxXaYwzrngdg2avmNqId9R4KESuM9GoHW0dszfyBCh5wYfeaffMElfDam3B92NUwyhZwtIiv623WVXY9PPz+EDjSJsIAu2Vi1vdJyA4nD4k9Lwmx/1zTc/UaYVLsiBqL2WdfvFTeoWoV+dNxQXSEPhB8gwi8x4O4lZW0cwVy/6fa8KMY8gZbcbSTr7U5bRERfW8l+jY+mYKQ/M/ccgpxaHiw1/+4LWfbJQ7VhJJrTyN0C36FQzY1URkSXg+53wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUmVEL4x6xdCqiqu2OBLs27EA8xGYwDQYJKoZIhvcNAQEMBQADggIBABCpITvO1+R4T9v2+onHiFxU5JjtCZ0zkXqRCMp/Z0UIYbeo1p07pZCPAUjBfGPCkAaR++OiG9sysALdJf8Y6HQKcyuAcWUqQnaIhoZ2JcAP7EKq7uCqsMhcYZD/j3O/3RPtSW5UOx6ItDU+Ua0t9Edho9whNw0VQXmo1JjYoP3FzPjuKoDWTSO1q5eYlZfwcTcs55O2shNkFafPg/6cCm5j6v9nyHrM3sk4LjkrBPUXVx2m/aoz219t8O9Ha9/CdMKXsPO/8gTUzpgnzSgPnGnBmi5xr1nspVN8X4E2f3D+DKqBim3YgslD68NcuFQvJ0/BxZzWVbrr+QXoyzaiCgXuogpIDc2bB6oRXqFnHNz36d4QJmJdWdSaijiS/peQ6EOPgOZ1GuObLWlDCBZLNeQ+N6QaiJxVO4XUj/s22i1IRtwdz84TRHrbWiIpEymsqmb/Ep5r4xV5d6+791axclfOTH7tQrY/SbPtTJI4OEgNekI8YfadQifpelF82MsFFEZuaQn0lj+fvLGtE/zKh3OdLTxRc5TAgBB+0T81+JQosygNr2aFFG0hxar1eyw/gLeG8H+7Ie50pyPvXO4OgB6Key8rSExpilQXlvAT1qX0qS3/K1i/9QkSE9ftIPT6vtwLV2sVQzfyanI4IZgWC6ryhvNLsRn0NFnQclor0+aq"
FastestvpnOpenvpnStaticKeyV1 = "697fe793b32cb5091d30f2326d5d124a9412e93d0a44ef7361395d76528fcbfc82c3859dccea70a93cfa8fae409709bff75f844cf5ff0c237f426d0c20969233db0e706edb6bdf195ec3dc11b3f76bc807a77e74662d9a800c8cd1144ebb67b7f0d3f1281d1baf522bfe03b7c3f963b1364fc0769400e413b61ca7b43ab19fac9e0f77e41efd4bda7fd77b1de2d7d7855cbbe3e620cecceac72c21a825b243e651f44d90e290e09c3ad650de8fca99c858bc7caad584bc69b11e5c9fd9381c69c505ec487a65912c672d83ed0113b5a74ddfbd3ab33b3683cec593557520a72c4d6cce46111f56f3396cc3ce7183edce553c68ea0796cf6c4375fad00aaa2a42"
)
func FastestvpnCountriesChoices(servers []models.FastestvpnServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Country
}
return makeUnique(choices)
}
func FastestvpnHostnameChoices(servers []models.FastestvpnServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Hostname
}
return makeUnique(choices)
}

View File

@@ -1,44 +0,0 @@
package constants
import (
"github.com/qdm12/gluetun/internal/models"
)
//nolint:lll
const (
HideMyAssCA = "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"
HideMyAssCertificate = "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"
HideMyAssRSAPrivateKey = "MIIEpAIBAAKCAQEA5XY3ERJYWs/YIeBoybivNlu+M32rJs+CAZsh7BnnetTxytI4ngsMRoqXETuis8udp2hsqEHsglLR9tlk9C8yCuKhxbkpdrXFWdISmUq5sa7/wqg/zJF1AZm5Jy0oHNyTHfG6XW61I/h9IN5dmcR9YLir8DVDBNllbtt0z+DnvOhYJOqC30ENahWkTmNKl1cT7EBrR5slddiBJleAb08z77pwsD310e6jWTBySsBcPy+xu/Jj2QgVil/3mstZZDI+noFzs3SkTFBkha/lNTP7NODBQ6m39iaJxz6ZR1xE3v7XU0H5WnpZIcQ2+kmu5Krk2y1GYMKL+9oaotXFPz9v+QIDAQABAoIBAQCcMcssOMOiFWc3MC3EWo4SP4MKQ9n0Uj5Z34LI151FdJyehlj54+VYQ1Cv71tCbjED2sZUBoP69mtsT/EzcsjqtfiOwgrifrs2+BOm+0HKHKiGlcbP9peiHkT10PxEITWXpYtJvGlbcfOjIxqt6B28cBjCK09ShrVQL9ylAKBearRRUacszppntMNTMtN/uG48ZR9Wm+xAczImdG6CrG5sLI/++JwM5PDChLvn5JgMGyOfQZdjNe1oSOVLmqFeG5uu/FS4oMon9+HtfjHJr4ZgA1yQ2wQh3GvEjlP8zwHxEpRJYbxpj6ZbjHZJ2HLX/Gcd9/cXiN8+fQ2zPIYQyG9dAoGBAPUUmt2nJNvl7gj0GbZZ3XR9o+hvj7bJ74W2NhMrw6kjrrzHTAUQd1sBQS8szAQCLqf2ou1aw9AMMBdsLAHydXxvbH7IBAla7rKr23iethtSfjhTNSgQLJHVZlNHfp3hzNtCQZ7j0qVjrteNotrdVF7kKPHDXAK00ICy6SPNjvrXAoGBAO+vdnO15jLeZbbi3lQNS4r8oCadyqyX7ouKE6MtKNhiPsNPGqHKiGcKs/+QylVgYvSmm7TgpsCAiEYeLSPT+Yq3y7HtwVpULlpfAhEJXmvn/6hGpOizx1WNGWhw7nHPWPDzf+jqCGzHdhK0aEZR3MZZQ+U+uKfGiJ8vrvgB7eGvAoGAWxxp5nU48rcsIw/8bxpBhgkfYk33M5EnBqKSv9XJS5wEXhIJZOiWNrLktNEGl4boKXE7aNoRacreJhcE1UR6AOS7hPZ+6atwiePyF4mJUeb9HZtxa493wk9/Vv6BR9il++1Jz/QKX4oLef8hyBP4Rb60qgxirG7kBLR+j9zfhskCgYEAzA5y5xIeuIIU0H4XUDG9dcebxSSjbwsuYIgeLdb9pjMGQhsvjjyyoh8/nT20tLkJpkXN3FFCRjNnUWLRhWYrVkkh1wqWiYOPrwqh5MU4KN/sDWSPcznTY+drkTpMFoKzsvdrl2zf3VR3FneXKv742bkXj601Ykko+XWMHcLutisCgYBSq8IrsjzfaTQiTGI9a7WWsvzK92bq7Abnfq7swAXWcJd/bnjTQKLrrvt2bmwNvlWKAb3c69BFMn0X4t4PuN0iJQ39D6aQAEaM7HwWAmjf5TbodbmgbGxdsUB4xcCIQQ1mvTkigXWrCg0YAD2GZSoaslXAAVv6nR5qWEIa0Hx9GA=="
)
func HideMyAssCountryChoices(servers []models.HideMyAssServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Country
}
return makeUnique(choices)
}
func HideMyAssRegionChoices(servers []models.HideMyAssServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Region
}
return makeUnique(choices)
}
func HideMyAssCityChoices(servers []models.HideMyAssServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].City
}
return makeUnique(choices)
}
func HideMyAssHostnameChoices(servers []models.HideMyAssServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Hostname
}
return makeUnique(choices)
}

View File

@@ -1,34 +0,0 @@
package constants
import (
"github.com/qdm12/gluetun/internal/models"
)
//nolint:lll
const (
IpvanishCA = "MIIErTCCA5WgAwIBAgIJAMYKzSS8uPKDMA0GCSqGSIb3DQEBDQUAMIGVMQswCQYDVQQGEwJVUzELMAkGA1UECBMCRkwxFDASBgNVBAcTC1dpbnRlciBQYXJrMREwDwYDVQQKEwhJUFZhbmlzaDEVMBMGA1UECxMMSVBWYW5pc2ggVlBOMRQwEgYDVQQDEwtJUFZhbmlzaCBDQTEjMCEGCSqGSIb3DQEJARYUc3VwcG9ydEBpcHZhbmlzaC5jb20wHhcNMTIwMTExMTkzMjIwWhcNMjgxMTAyMTkzMjIwWjCBlTELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkZMMRQwEgYDVQQHEwtXaW50ZXIgUGFyazERMA8GA1UEChMISVBWYW5pc2gxFTATBgNVBAsTDElQVmFuaXNoIFZQTjEUMBIGA1UEAxMLSVBWYW5pc2ggQ0ExIzAhBgkqhkiG9w0BCQEWFHN1cHBvcnRAaXB2YW5pc2guY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAt9DBWNr/IKOuY3TmDP5x7vYZR0DGxLbXU8TyAzBbjUtFFMbhxlHiXVQrZHmgzih94x7BgXM7tWpmMKYVb+gNaqMdWE680Qm3nOwmhy/dulXDkEHAwD05i/iTx4ZaUdtV2vsKBxRg1vdC4AEiwD7bqV4HOi13xcG971aQ55Mj1KeCdA0aNvpat1LWx2jjWxsfI8s2Lv5Fkoi1HO1+vTnnaEsJZrBgAkLXpItqP29Lik3/OBIvkBIxlKrhiVPixE5qNiD+eSPirsmROvsyIonoJtuY4Dw5K6pcNlKyYiwo1IOFYU3YxffwFJk+bSW4WVBhsdf5dGxq/uOHmuz5gdwxCwIDAQABo4H9MIH6MAwGA1UdEwQFMAMBAf8wHQYDVR0OBBYEFEv9FCWJHefBcIPX9p8RHCVOGe6uMIHKBgNVHSMEgcIwgb+AFEv9FCWJHefBcIPX9p8RHCVOGe6uoYGbpIGYMIGVMQswCQYDVQQGEwJVUzELMAkGA1UECBMCRkwxFDASBgNVBAcTC1dpbnRlciBQYXJrMREwDwYDVQQKEwhJUFZhbmlzaDEVMBMGA1UECxMMSVBWYW5pc2ggVlBOMRQwEgYDVQQDEwtJUFZhbmlzaCBDQTEjMCEGCSqGSIb3DQEJARYUc3VwcG9ydEBpcHZhbmlzaC5jb22CCQDGCs0kvLjygzANBgkqhkiG9w0BAQ0FAAOCAQEAI2dkh/43ksV2fdYpVGhYaFZPVqCJoToCez0IvOmLeLGzow+EOSrY508oyjYeNP4VJEjApqo0NrMbKl8g/8bpLBcotOCF1c1HZ+y9v7648uumh01SMjsbBeHOuQcLb+7gX6c0pEmxWv8qj5JiW3/1L1bktnjW5Yp5oFkFSMXjOnIoYKHyKLjN2jtwH6XowUNYpg4qVtKU0CXPdOznWcd9/zSfa393HwJPeeVLbKYaFMC4IEbIUmKYtWyoJ9pJ58smU3pWsHZUg9Zc0LZZNjkNlBdQSLmUHAJ33Bd7pJS0JQeiWviC+4UTmzEWRKa7pDGnYRYNu2cUo0/voStphv8EVA=="
)
func IpvanishCountryChoices(servers []models.IpvanishServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Country
}
return makeUnique(choices)
}
func IpvanishCityChoices(servers []models.IpvanishServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].City
}
return makeUnique(choices)
}
func IpvanishHostnameChoices(servers []models.IpvanishServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Hostname
}
return makeUnique(choices)
}

View File

@@ -1,43 +0,0 @@
package constants
import (
"github.com/qdm12/gluetun/internal/models"
)
//nolint:lll
const (
IvpnCA = "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"
IvpnOpenvpnStaticKeyV1 = "ac470c93ff9f5602a8aab37dee84a52814d10f20490ad23c47d5d82120c1bf859e93d0696b455d4a1b8d55d40c2685c41ca1d0aef29a3efd27274c4ef09020a3978fe45784b335da6df2d12db97bbb838416515f2a96f04715fd28949c6fe296a925cfada3f8b8928ed7fc963c1563272f5cf46e5e1d9c845d7703ca881497b7e6564a9d1dea9358adffd435295479f47d5298fabf5359613ff5992cb57ff081a04dfb81a26513a6b44a9b5490ad265f8a02384832a59cc3e075ad545461060b7bcab49bac815163cb80983dd51d5b1fd76170ffd904d8291071e96efc3fb777856c717b148d08a510f5687b8a8285dcffe737b98916dd15ef6235dee4266d3b"
)
func IvpnCountryChoices(servers []models.IvpnServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Country
}
return makeUnique(choices)
}
func IvpnCityChoices(servers []models.IvpnServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].City
}
return makeUnique(choices)
}
func IvpnISPChoices(servers []models.IvpnServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].ISP
}
return makeUnique(choices)
}
func IvpnHostnameChoices(servers []models.IvpnServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Hostname
}
return makeUnique(choices)
}

View File

@@ -1,42 +0,0 @@
package constants
import (
"github.com/qdm12/gluetun/internal/models"
)
//nolint:lll
const (
MullvadCertificate = "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"
)
func MullvadCountryChoices(servers []models.MullvadServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Country
}
return makeUnique(choices)
}
func MullvadCityChoices(servers []models.MullvadServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].City
}
return makeUnique(choices)
}
func MullvadHostnameChoices(servers []models.MullvadServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Hostname
}
return makeUnique(choices)
}
func MullvadISPChoices(servers []models.MullvadServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].ISP
}
return makeUnique(choices)
}

View File

@@ -1,27 +0,0 @@
package constants
import (
"github.com/qdm12/gluetun/internal/models"
)
//nolint:lll
const (
NordvpnCertificate = "MIIFCjCCAvKgAwIBAgIBATANBgkqhkiG9w0BAQ0FADA5MQswCQYDVQQGEwJQQTEQMA4GA1UEChMHTm9yZFZQTjEYMBYGA1UEAxMPTm9yZFZQTiBSb290IENBMB4XDTE2MDEwMTAwMDAwMFoXDTM1MTIzMTIzNTk1OVowOTELMAkGA1UEBhMCUEExEDAOBgNVBAoTB05vcmRWUE4xGDAWBgNVBAMTD05vcmRWUE4gUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMkr/BYhyo0F2upsIMXwC6QvkZps3NN2/eQFkfQIS1gql0aejsKsEnmY0Kaon8uZCTXPsRH1gQNgg5D2gixdd1mJUvV3dE3y9FJrXMoDkXdCGBodvKJyU6lcfEVF6/UxHcbBguZK9UtRHS9eJYm3rpL/5huQMCppX7kUeQ8dpCwd3iKITqwd1ZudDqsWaU0vqzC2H55IyaZ/5/TnCk31Q1UP6BksbbuRcwOVskEDsm6YoWDnn/IIzGOYnFJRzQH5jTz3j1QBvRIuQuBuvUkfhx1FEwhwZigrcxXuMP+QgM54kezgziJUaZcOM2zF3lvrwMvXDMfNeIoJABv9ljw969xQ8czQCU5lMVmA37ltv5Ec9U5hZuwk/9QO1Z+d/r6Jx0mlurS8gnCAKJgwa3kyZw6e4FZ8mYL4vpRRhPdvRTWCMJkeB4yBHyhxUmTRgJHm6YR3D6hcFAc9cQcTEl/I60tMdz33G6m0O42sQt/+AR3YCY/RusWVBJB/qNS94EtNtj8iaebCQW1jHAhvGmFILVR9lzD0EzWKHkvyWEjmUVRgCDd6Ne3eFRNS73gdv/C3l5boYySeu4exkEYVxVRn8DhCxs0MnkMHWFK6MyzXCCn+JnWFDYPfDKHvpff/kLDobtPBf+Lbch5wQy9quY27xaj0XwLyjOltpiSTLWae/Q4vAgMBAAGjHTAbMAwGA1UdEwQFMAMBAf8wCwYDVR0PBAQDAgEGMA0GCSqGSIb3DQEBDQUAA4ICAQC9fUL2sZPxIN2mD32VeNySTgZlCEdVmlq471o/bDMP4B8gnQesFRtXY2ZCjs50Jm73B2LViL9qlREmI6vE5IC8IsRBJSV4ce1WYxyXro5rmVg/k6a10rlsbK/eg//GHoJxDdXDOokLUSnxt7gk3QKpX6eCdh67p0PuWm/7WUJQxH2SDxsT9vB/iZriTIEe/ILoOQF0Aqp7AgNCcLcLAmbxXQkXYCCSB35Vp06u+eTWjG0/pyS5V14stGtw+fA0DJp5ZJV4eqJ5LqxMlYvEZ/qKTEdoCeaXv2QEmN6dVqjDoTAok0t5u4YRXzEVCfXAC3ocplNdtCA72wjFJcSbfif4BSC8bDACTXtnPC7nD0VndZLp+RiNLeiENhk0oTC+UVdSc+n2nJOzkCK0vYu0Ads4JGIB7g8IB3z2t9ICmsWrgnhdNdcOe15BincrGA8avQ1cWXsfIKEjbrnEuEk9b5jel6NfHtPKoHc9mDpRdNPISeVawDBM1mJChneHt59Nh8Gah74+TM1jBsw4fhJPvoc7Atcg740JErb904mZfkIEmojCVPhBHVQ9LHBAdM8qFI2kRK0IynOmAZhexlP/aT/kpEsEPyaZQlnBn3An1CRz8h0SPApL8PytggYKeQmRhl499+6jLxcZ2IegLfqq41dzIjwHwTMplg+1pKIOVojpWA=="
NordvpnOpenvpnStaticKeyV1 = "e685bdaf659a25a200e2b9e39e51ff030fc72cf1ce07232bd8b2be5e6c670143f51e937e670eee09d4f2ea5a6e4e69965db852c275351b86fc4ca892d78ae002d6f70d029bd79c4d1c26cf14e9588033cf639f8a74809f29f72b9d58f9b8f5fefc7938eade40e9fed6cb92184abb2cc10eb1a296df243b251df0643d53724cdb5a92a1d6cb817804c4a9319b57d53be580815bcfcb2df55018cc83fc43bc7ff82d51f9b88364776ee9d12fc85cc7ea5b9741c4f598c485316db066d52db4540e212e1518a9bd4828219e24b20d88f598a196c9de96012090e333519ae18d35099427e7b372d348d352dc4c85e18cd4b93f8a56ddb2e64eb67adfc9b337157ff4"
)
func NordvpnRegionChoices(servers []models.NordvpnServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Region
}
return makeUnique(choices)
}
func NordvpnHostnameChoices(servers []models.NordvpnServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Hostname
}
return makeUnique(choices)
}

View File

@@ -1,16 +0,0 @@
package constants
const (
AES128cbc = "aes-128-cbc"
AES256cbc = "aes-256-cbc"
AES128gcm = "aes-128-gcm"
AES256gcm = "aes-256-gcm"
SHA1 = "sha1"
SHA256 = "sha256"
SHA512 = "sha512"
)
const (
Openvpn24 = "2.4"
Openvpn25 = "2.5"
)

Some files were not shown because too many files have changed in this diff Show More