Compare commits

..

217 Commits

Author SHA1 Message Date
Quentin McGaw
d0f5d0c007 fix(version): log last release by tag name alphabetically instead of date 2024-08-09 07:46:19 +00:00
dependabot[bot]
06949554dd Chore(deps): Bump github.com/breml/rootcerts from 0.2.16 to 0.2.17 (#2316) 2024-08-09 07:29:49 +00:00
Quentin McGaw
8e8cbe0d37 fix(privatevpn): set openvpn vpn type for no hostname server 2024-08-09 07:29:22 +00:00
Quentin McGaw
3d72f6092b fix(firewall): VPN_PORT_FORWARDING_LISTENING_PORT behavior fixed
by not restricting the destination address to 127.0.0.1
2024-08-09 07:29:22 +00:00
Quentin McGaw
0ac047f9de fix(format-servers): add missing vpn type column for natively supported providers
- nordvpn
- surfshark
2024-08-09 07:29:22 +00:00
Quentin McGaw
129002a058 fix(custom-openvpn): remove comments before parsing file 2024-08-09 07:29:22 +00:00
Quentin McGaw
e95095ad5c fix(config): log out if port forwarding only servers is enabled 2024-08-09 07:29:22 +00:00
Quentin McGaw
efecb0ea0e fix(publicip): abort ip data fetch if vpn context is canceled
- Prevents requesting the public IP address N times after N VPN failures
- Fetching runs with a context local to the 'single run'
- Single run writes single run result to a channel back to the caller, RunOnce is now blocking
2024-08-09 07:29:22 +00:00
Quentin McGaw
b504227625 fix(ipv6): detect ignoring loopback route destinations 2024-08-09 07:29:22 +00:00
Quentin McGaw
2b298de59d fix(torguard): update OpenVPN configuration
- add aes-128-gcm and aes-128-cbc ciphers
- add extra CA certificate value
- remove mssfix option
- remove sndbuf and rcvbuf ption
- remove ping option
- remove reneg option
2024-08-09 07:29:22 +00:00
Quentin McGaw
3d4c4e505a fix(custom): do not set server name if it does not exist 2024-08-09 07:29:22 +00:00
Quentin McGaw
948ebdc4e1 fix(vpnunlimited): allow OpenVPN TCP on port 1197 2024-08-09 07:29:22 +00:00
Quentin McGaw
7cf46a0ac3 fix(vpnunlimited): change UDP port from 1194 to 1197 2024-08-09 07:29:22 +00:00
Quentin McGaw
faa8340eea fix(custom): parse port option line for OpenVPN 2024-08-09 07:29:22 +00:00
Quentin McGaw
8267d0e3bd fix(custom): set server name if names filter is not empty
- fix PIA port forwarding code usage
- refers to #2147
2024-08-09 07:29:22 +00:00
Quentin McGaw
f04ac56454 feat(airvpn): set default mssfix to 1320-28 2024-08-09 07:29:22 +00:00
Quentin McGaw
d9997f5234 chore(vpnsecure): associate "N / A" with no data for servers 2024-08-09 07:29:22 +00:00
Quentin McGaw
ac5c13e9c9 fix(surfshark): remove outdated hardcoded retro servers 2024-08-09 07:29:22 +00:00
Quentin McGaw
7570a79ce6 fix(tun): only create tun device if it does not exist
- do not create if it exists and is problematic
- wrap errors with a better context
2024-08-09 07:29:22 +00:00
Quentin McGaw
c47858fdda fix(server): /openvpn route status get and put
- get status return stopped if running wireguard
- put status changes vpn type if running wireguard
2024-08-09 07:29:22 +00:00
Adam Hebden
b3ceece779 feat(nordvpn): filter with SERVER_CATEGORIES (#1806)
- update NordVPN servers data built-in
2024-03-22 10:02:31 +01:00
Quentin McGaw
c74e4178bb feat(nordvpn): update mechanism uses v2 API 2024-03-21 17:02:25 +00:00
Quentin McGaw
c0621bf381 chore(lint): upgrade linter to v1.56.2 2024-03-21 17:02:11 +00:00
Dennis Gaida
fb00fb16c2 feat(settings): load wireguard individual fields as secret files (#1348)
- Private key from `/run/secrets/wireguard_private_key` (path configurable with `WIREGUARD_PRIVATE_KEY_SECRETFILE`)
- Preshared key from `/run/secrets/wireguard_preshared_key` (path configurable with `WIREGUARD_PRESHARED_KEY_SECRETFILE`)
- Addresses from `/run/secrets/wireguard_addresses` (path configurable with `WIREGUARD_ADDRESSES_SECRETFILE`)
2024-03-21 10:08:41 +01:00
Quentin McGaw
6096b7ad4b feat(config): read Wireguard config from secret
- defaults to `/run/secrets/wg0.conf`
- can be changed with variable `WIREGUARD_CONF_SECRETFILE`
2024-03-21 08:18:14 +00:00
Quentin McGaw
9cb4c74493 chore(ci): pin docker/build-push-action to v5 2024-03-21 07:36:56 +00:00
Quentin McGaw
e470dc8a12 chore(ci): add opened issue workflow 2024-03-21 07:33:47 +00:00
dependabot[bot]
ab49f1f733 Chore(deps): Bump github.com/breml/rootcerts from 0.2.14 to 0.2.16 (#2094) 2024-03-21 08:33:38 +01:00
Quentin McGaw
62158a1739 hotfix(settings): copy port forward only field 2024-03-19 15:20:04 +00:00
Quentin McGaw
3d16798544 fix(torguard): set user agent to download zip files 2024-03-18 17:46:49 +00:00
Anton Nesterov
b51aa0c6b9 feat(pia): PORT_FORWARD_ONLY variable (#2070) 2024-03-18 18:40:09 +01:00
Anton Nesterov
84d00b42f1 fix(config): STREAM_ONLY should set StreamOnly flag for server selection (#2126) 2024-03-18 16:01:00 +01:00
Quentin McGaw
e201856667 fix(ci): set issue number correctly for closed issue workflow 2024-03-07 12:33:09 +00:00
Quentin McGaw
3254fc8aa6 feat(servers): update vyprnvpn data 2024-03-07 12:29:15 +00:00
Quentin McGaw
4bca4ca932 chore(github): add closed issue workflow 2024-02-21 17:41:41 +00:00
Quentin McGaw
a20695ffb3 feat(servers): update vpn unlimited data 2024-02-21 16:58:12 +00:00
Quentin McGaw
d01cfef039 feat(servers): update pia data 2024-02-21 16:54:49 +00:00
Quentin McGaw
0eed558b10 feat(log): change unhealthy log to debug level 2024-02-21 16:44:35 +00:00
Quentin McGaw
423a5c37e0 feat(publicip): PUBLICIP_API variable supporting ipinfo and ip2location 2024-02-14 07:36:45 +00:00
Quentin McGaw
cfca026621 chore(publicip): less coupling with ipinfo.io 2024-02-14 07:30:02 +00:00
Quentin McGaw
6a6337b98f feat(publicip/ipinfo): add PUBLICIP_API_TOKEN variable 2024-02-13 10:55:06 +00:00
Quentin McGaw
72b5afc771 fix(privado): update Zip file URL and update servers data
- Fix Bug: privado update url outdated #2104
2024-02-13 10:22:49 +00:00
Quentin McGaw
659bc0c9cb fix(surfshark): remove no longer valid multi hop regions 2024-02-07 08:36:33 +00:00
Quentin McGaw
827e591174 chore(settings): clearer error message for surfshark regions
- only log possible 'new' server regions
- do not log old retro-compatible server regions
- maintains compatibility with older names
2024-02-06 20:39:00 +00:00
Quentin McGaw
a369745101 chore(surfshark): fail validation for empty string region 2024-02-06 20:36:30 +00:00
Quentin McGaw
586b0e17a0 chore(health): more obvious log to see the health guide and to reduce issues 2024-02-03 20:13:05 +00:00
Quentin McGaw
b5f1055682 hotfix(settings): allow public IP outbound subnets but not the unspecified address 2024-02-03 20:02:43 +00:00
Quentin McGaw
6b9c775055 feat(settings): prevent public firewall outbound subnets 2024-01-29 18:26:23 +00:00
Quentin McGaw
d8b9b2a85b feat(natpmp): rpc error contain all failed attempt messages 2024-01-19 16:44:45 +00:00
Quentin McGaw
c826707d42 fix(vpnunlimited): specify OpenVPN cipher and auth 2024-01-01 18:21:24 +00:00
Quentin McGaw
8a17cd87c3 feat(windscribe): update servers data 2024-01-01 18:09:05 +00:00
Quentin McGaw
f8da1e79bc fix(vpnunlimited): remove DEFAULT:@SECLEVEL=0 2023-12-22 09:39:34 +00:00
Quentin McGaw
cfc29d6a6b feat(vpnunlimited): add second CA certificate for OpenVPN 2023-12-19 18:21:57 +00:00
Quentin McGaw
5467652b8b chore(openvpn): support multiple CAs in generated config 2023-12-19 18:21:03 +00:00
Quentin McGaw
daa63c276d fix(vpnunlimited): update CA certificate 2023-12-19 18:15:56 +00:00
dependabot[bot]
ab96acdc5b Chore(deps): Bump golang.org/x/crypto from 0.16.0 to 0.17.0 (#2012) 2023-12-19 19:07:14 +01:00
dependabot[bot]
6e108706a1 Chore(deps): Bump docker/build-push-action from 5.0.0 to 5.1.0 (#1969) 2023-12-19 19:07:03 +01:00
Quentin McGaw
4a6c229504 fix(settings): add VPN Unlimited warning for OpenSSL 3 2023-12-15 10:32:53 +00:00
dependabot[bot]
ed3a72790a Chore(deps): Bump DavidAnson/markdownlint-cli2-action from 13 to 14 (#1982) 2023-12-14 17:08:12 +01:00
dependabot[bot]
4bf5777f23 Chore(deps): Bump golang.org/x/net from 0.18.0 to 0.19.0 (#1985) 2023-12-14 17:07:43 +01:00
dependabot[bot]
f0f9bdb883 Chore(deps): Bump github.com/klauspost/compress from 1.17.2 to 1.17.4 (#1993) 2023-12-14 17:07:33 +01:00
dependabot[bot]
4984d90b5a Chore(deps): Bump github/codeql-action from 2 to 3 (#2002) 2023-12-14 17:07:20 +01:00
dependabot[bot]
b5e648d13a Chore(deps): Bump github.com/breml/rootcerts from 0.2.13 to 0.2.14 (#1981) 2023-12-14 16:18:49 +01:00
yifangd
f71a1b083b fix(purevpn): fix update url and update servers (#1992)
See https://support.purevpn.com/purevpn/openvpn-files
2023-12-14 16:18:32 +01:00
Quentin McGaw
75fd869625 fix(firewall): handle OpenVPN tcp-client as tcp 2023-12-14 15:10:33 +00:00
Quentin McGaw
657b4b787f fix(custom): read wireguard presharedkey from peer section 2023-12-09 17:26:01 +00:00
Quentin McGaw
32d6453918 fix(custom): default TCP port for any tcp proto 2023-11-28 07:51:29 +00:00
Quentin McGaw
c326b616b4 feat(custom): support tcp-client proto for OpenVPN 2023-11-28 07:48:55 +00:00
Quentin McGaw
d5376629df fix(format-servers): add server name header for PIA 2023-11-28 07:31:36 +00:00
Quentin McGaw
3e825d7a08 fix(format-servers): for providers with dashes 2023-11-28 07:27:29 +00:00
dependabot[bot]
059b12883f Chore(deps): Bump golang.org/x/text from 0.13.0 to 0.14.0 (#1946) 2023-11-23 08:37:45 +00:00
dependabot[bot]
74aa509644 Chore(deps): Bump golang.org/x/net from 0.17.0 to 0.18.0 (#1953) 2023-11-23 08:37:45 +00:00
Quentin McGaw
4105f74ce1 feat(portforward): port redirection with VPN_PORT_FORWARDING_LISTENING_PORT 2023-11-23 08:37:43 +00:00
Quentin McGaw
8318be3159 feat(nordvpn): add access token warning if used as wireguard private key 2023-11-08 10:07:02 +00:00
Quentin McGaw
de196490db fix(settings): wireguard preshared key from toml file 2023-11-08 10:06:57 +00:00
dependabot[bot]
ab7d1ccf3d Chore(deps): Bump github.com/fatih/color from 1.15.0 to 1.16.0 (#1950) 2023-11-08 10:36:05 +01:00
dependabot[bot]
ed49a7a7c0 Chore(deps): Bump docker/login-action from 2 to 3 (#1936) 2023-11-01 14:39:03 +01:00
dependabot[bot]
135832d985 Chore(deps): Bump docker/metadata-action from 4 to 5 (#1937) 2023-11-01 14:38:54 +01:00
dependabot[bot]
1adbd9f692 Chore(deps): Bump docker/setup-buildx-action from 2 to 3 (#1938) 2023-11-01 14:38:46 +01:00
Quentin McGaw
26e1c92841 fix(shadowsocks): bump from v0.5.0-rc1 to v0.5.0
- treat udp read error as non critical
- log out crash error for tcpudp combined server
2023-11-01 13:38:30 +00:00
dependabot[bot]
3c5b3514fb Chore(deps): Bump actions/checkout from 3 to 4 (#1847) 2023-10-31 16:04:29 +01:00
dependabot[bot]
f884293f6e Chore(deps): Bump github.com/breml/rootcerts from 0.2.11 to 0.2.13 (#1800) 2023-10-31 16:03:28 +01:00
dependabot[bot]
c67bd1aa2a Chore(deps): Bump golang.org/x/text from 0.11.0 to 0.13.0 (#1845) 2023-10-31 16:03:16 +01:00
dependabot[bot]
77ace9377d Chore(deps): Bump golang.org/x/net from 0.12.0 to 0.17.0 (#1907) 2023-10-31 16:02:46 +01:00
dependabot[bot]
6e676209ff Chore(deps): Bump docker/setup-qemu-action from 2 to 3 (#1861) 2023-10-31 14:08:18 +01:00
dependabot[bot]
80917d58b2 Chore(deps): Bump docker/build-push-action from 4.1.1 to 5.0.0 (#1860) 2023-10-31 14:08:08 +01:00
dependabot[bot]
fa49f13f19 Chore(deps): Bump crazy-max/ghaction-github-labeler from 4 to 5 (#1858) 2023-10-31 14:07:35 +01:00
dependabot[bot]
1fcabd152f Chore(deps): Bump DavidAnson/markdownlint-cli2-action from 11 to 13 (#1871) 2023-10-31 14:07:25 +01:00
dependabot[bot]
385879c297 Chore(deps): Bump github.com/klauspost/compress from 1.16.7 to 1.17.2 (#1922) 2023-10-31 14:06:59 +01:00
dependabot[bot]
e0515cb458 Chore(deps): Bump golang.org/x/sys from 0.11.0 to 0.13.0 (#1897) 2023-10-31 14:06:48 +01:00
Quentin McGaw
1c43a1d55b fix(portforward): service start error not treated as critical
A service start error can happen if the service is started after the Wireguard VPN tunnel is up, but the tunnel does not work. The VPN is then internally restarted, causing the service start error, so it should not be treated as a critical error.
2023-10-07 13:21:32 +00:00
Quentin McGaw
6c639fcf7f fix(publicip): do not retry on too many requests 2023-10-07 12:59:43 +00:00
Quentin McGaw
ec1f252528 fix(portforward): different validation when vpn is up or not 2023-10-07 12:43:36 +00:00
Quentin McGaw
ee413f59a2 fix(protonvpn): set natpmp external port to 1 2023-10-06 16:09:05 +00:00
Quentin McGaw
d4df87286e fix(portforward): trigger after VPN restart 2023-09-28 14:00:58 +00:00
Quentin McGaw
a194906bdd chore(protonvpn): add debug logs for keeping port forwarded 2023-09-28 07:08:07 +00:00
Quentin McGaw
9b00763a69 feat(config): add /32 if not present for Wireguard addresses 2023-09-24 16:50:34 +00:00
Quentin McGaw
4d627bb7b1 feat(protonvpn): port forwarding connection refused error points to add +pmp to OpenVPN user 2023-09-24 15:15:05 +00:00
Quentin McGaw
dc8fc5f81f feat(updater): log warning about using -minratio 2023-09-24 15:05:39 +00:00
Quentin McGaw
b787e12e25 feat(surfshark): update servers data 2023-09-24 15:02:08 +00:00
Quentin McGaw
f96448947f fix(publicip): rework run loop and fix restarts
- Clearing IP data on VPN disconnection clears file
- More efficient partial updates
- Fix loop exit
- Validate settings before updating
2023-09-24 14:55:51 +00:00
Quentin McGaw
e64e5af4c3 chore(portforward): improve loop reliability
- handle settings update within run function
- signal back start result to update call
- update loop settings only when service is started
2023-09-24 10:28:10 +00:00
Quentin McGaw
aa6dc786a4 chore(provider): use type assertion for port forwarders 2023-09-23 13:02:09 +00:00
Quentin McGaw
84300db7c1 fix(portforward): restart service on run error
- fix when port assigned changes
2023-09-23 12:39:49 +00:00
Quentin McGaw
2ac0f35060 fix(protonvpn): crash service if port assigned changes 2023-09-23 12:36:13 +00:00
Quentin McGaw
1a865f56d5 chore(vpn): fix typo portForwader 2023-09-23 12:03:56 +00:00
Quentin McGaw
0406de399d chore(portforward): move vpn gateway obtention within port forwarding service 2023-09-23 12:03:06 +00:00
Quentin McGaw
71201411f4 fix(portforward): rework run loop and fix deadlocks (#1874) 2023-09-23 12:57:12 +02:00
Quentin McGaw
c435bbb32c docs(issue): provide minimum requirements for an issue
- title must be filled
- at least 10 lines of log provided
- Gluetun version must be provided
2023-09-22 09:22:13 +00:00
Quentin McGaw
4cbfea41f2 docs(issues): add Unraid as option in bug template 2023-09-22 09:16:44 +00:00
Quentin McGaw
f9c9ad34f7 feat(protonvpn): check udp vs tcp port forwarded 2023-09-22 08:50:19 +00:00
Quentin McGaw
4ea474b896 fix(routing): change firewall only for matching ip families 2023-09-20 10:45:13 +00:00
Quentin McGaw
6aa4a93665 change(format): use dashes instead of spaces for provider names
- `-private\ internet\ access` -> `private-internet-access`
- `-perfect\ privacy` -> `-perfect-privacy`
- `-vpn\ unlimited` -> `-vpn-unlimited`
2023-09-20 10:24:32 +00:00
Quentin McGaw
ea25a0ff89 fix(protonvpn): natpmp assigned ports logs removed 2023-09-20 09:51:13 +00:00
Quentin McGaw
659da67ed5 feat(cyberghost): update servers data 2023-09-20 09:35:28 +00:00
Quentin McGaw
ffc6d2e593 chore(lint): upgrade linter to v1.54.1 2023-09-20 09:34:32 +00:00
Quentin McGaw
03ce08e23d chore(build): upgrade Go to 1.21 2023-09-20 09:34:29 +00:00
Aleksa Siriški
3449e7a0e1 fix(publicip): IPv6 endpoint for ipinfo (#1853) 2023-09-13 16:37:39 +02:00
Quentin McGaw
c0062fb807 fix(protonvpn): natpmp check for assigned internal port 2023-09-13 14:18:35 +00:00
dependabot[bot]
1ac031e78c Chore(deps): Bump golang.org/x/sys from 0.10.0 to 0.11.0 (#1786) 2023-08-24 02:04:07 -07:00
Quentin McGaw
e556871e8b change(dns): DNS_KEEP_NAMESERVER leaves DNS fully untouched 2023-08-11 11:03:40 +00:00
Quentin McGaw
082a38b769 fix(netlink): try loading Wireguard module if not found (#1741) 2023-08-04 13:09:56 +02:00
Quentin McGaw
39ae57f49d fix(routing): add outbound subnets routes only for matching ip families 2023-07-28 07:24:26 +00:00
Quentin McGaw
9024912e17 fix(custom): allow custom endpoint port setting 2023-07-27 10:32:08 +00:00
Quentin McGaw
eecfb3952f chore(settings): change source precedence order
1. Secret files (program scope)
2. Files (program scope)
3. Environment variables (OS scope)
Fix #1759
2023-07-22 16:02:32 +00:00
Quentin McGaw
0ebfe534d3 feat(settings): parse Wireguard settings from /gluetun/wireguard/wg0.conf (#1120) 2023-07-22 17:25:30 +02:00
eiqnepm
c5cc240a6c feat(surfshark): update API endpoint and servers data (#1560) 2023-07-21 20:21:46 +02:00
Quentin McGaw
1a5a0148ea feat(torguard): update severs data 2023-07-18 16:02:06 +00:00
Quentin McGaw
abe2aceb18 feat(wireguard): clarify wireguard is up message 2023-07-18 15:53:39 +00:00
Quentin McGaw
fa541b8fc2 chore(deps): bump gosettings to v0.4.0-rc1 2023-07-11 13:26:55 +00:00
dependabot[bot]
a681d38dfb Chore(deps): Bump golang.org/x/net from 0.10.0 to 0.12.0 (#1729) 2023-07-09 14:22:14 +02:00
dependabot[bot]
a7b96e3f4d Chore(deps): Bump golang.org/x/sys from 0.8.0 to 0.10.0 (#1732) 2023-07-07 15:32:23 +02:00
dependabot[bot]
04ef92edab Chore(deps): Bump golang.org/x/text from 0.10.0 to 0.11.0 (#1726) 2023-07-07 12:56:47 +02:00
Quentin McGaw
919b55c3aa feat(wireguard): WIREGUARD_ALLOWED_IPS variable (#1291) 2023-07-06 09:08:59 +02:00
Quentin McGaw
9c0f187a12 chore(natpmp): more robust tests with longer connection durations 2023-07-06 06:54:01 +00:00
Quentin McGaw
075a1e2a80 chore(natpmp): initialRetry -> initialConnectionDuration 2023-07-06 06:50:17 +00:00
Quentin McGaw
f31a846cda chore(ci): add markdown-skip workflow 2023-07-05 15:45:46 +00:00
Quentin McGaw
9bef46db77 chore(ci): trigger markdown on pull requests
- Verification steps
- Publishing step to Docker Hub is reserved for pushes to the master branch
2023-07-05 15:44:33 +00:00
Quentin McGaw
d83217f7ac chore(ci): add markdown dead link checking 2023-07-05 14:47:52 +00:00
Quentin McGaw
1cd2fec796 chore(ci): add markdown linting to markdown workflow 2023-07-05 14:31:09 +00:00
Quentin McGaw
235f24ee5b chore(ci): add misspell action to markdown job 2023-07-05 14:28:56 +00:00
Quentin McGaw
2e34c6009e chore(ci): Markdown workflow triggers on *.md files 2023-07-05 14:28:50 +00:00
Quentin McGaw
c0eb2f2315 chore(ci): rename workflow to Markdown 2023-07-05 14:27:14 +00:00
Quentin McGaw
8ad16cdc12 feat(protonvpn): port forwarding support with NAT-PMP (#1543)
Co-authored-by: Nicholas Xavier <nicho@nicho.dev>
2023-06-30 20:09:44 +02:00
Quentin McGaw
fae6544431 feat(pf): VPN_PORT_FORWARDING_PROVIDER variable (#1616) 2023-06-30 19:24:01 +02:00
Quentin McGaw
f8a41b2133 fix(protonvpn): add aes-256-gcm cipher for openvpn 2023-06-30 17:14:44 +00:00
Quentin McGaw
ff9b56d6d8 docs(all): update to use newer wiki repository
- Update URLs logged by program
- Update README.md links
- Update contributing guide link
- Update issue templates links
- Replace Wiki issue template by link to Gluetun Wiki repository issue creation
- Set program announcement about Github wiki new location
2023-06-30 10:31:26 +00:00
Quentin McGaw
99d5a591b9 docs(readme): fixes and small changes
- remove `UPDATER_VPN_SERVICE_PROVIDERS` in docker-compose config
- remove Slack channel link (don't have time to check it)
- Update Wireguard native integrations support list
2023-06-29 16:28:24 +00:00
Quentin McGaw
fbe252a9b6 chore(Docker): add missing environment variables
- `OPENVPN_PROCESS_USER` defaults to `root`
- Add `HTTPPROXY_STEALTH=off`
- Add `HTTP_CONTROL_SERVER_LOG=on`
2023-06-29 16:20:25 +00:00
Quentin McGaw
76a92b90e3 fix(routing): VPNLocalGatewayIP Wireguard support 2023-06-28 14:23:34 +00:00
Quentin McGaw
2873b06275 fix(wireguard): wrap setupIPv6 rule error correctly 2023-06-28 13:08:23 +00:00
Quentin McGaw
9cdd6294d2 feat(mullvad): update servers data 2023-06-28 13:06:40 +00:00
dependabot[bot]
44bc60b00d Chore(deps): Bump docker/build-push-action from 4.0.0 to 4.1.1 (#1684) 2023-06-28 14:28:59 +02:00
dependabot[bot]
6f0be57860 Chore(deps): Bump golang.org/x/text from 0.9.0 to 0.10.0 (#1681) 2023-06-28 14:28:44 +02:00
Quentin McGaw
d3d8484b8e hotfix(env): case sensitivity for OPENVPN_CUSTOM_CONFIG 2023-06-28 12:27:13 +00:00
Quentin McGaw
515ae8efb3 hotfix(nordvpn): update url 2023-06-18 11:00:36 +00:00
Quentin McGaw
83826e1253 hotfix(settings): fix godot lint error 2023-06-12 13:51:50 +00:00
Quentin McGaw
4292a500ae fix(wireguard): delete existing Wireguard link before adding it 2023-06-10 20:23:21 +00:00
Quentin McGaw
4a0f9c36ba hotfix(nordvpn): accept countries in SERVER_REGIONS 2023-06-10 16:29:30 +00:00
Quentin McGaw
ea1991496e hotfix(routing): remove debug prints 2023-06-08 22:44:08 +00:00
Quentin McGaw
4675572328 hotfix(routing): change main table from 0 to 254 2023-06-08 20:03:07 +00:00
Quentin McGaw
412921fc1f hotfix(routing): ignore non-main table for routes
- When searching for default routes
- When searching for local networks
2023-06-08 19:50:42 +00:00
Quentin McGaw
1c905d0e6f chore(labels): add problem category labels
- Config problem
- Routing
- IPv6
- Port forwarding
2023-06-08 10:04:09 +00:00
Quentin McGaw
2ec9293324 feat(wireguard): MTU defaults to 1400 instead of 1420 2023-06-08 09:50:21 +00:00
Quentin McGaw
9b39a301a8 chore(routing): remove unused VPNDestinationIP 2023-06-08 09:17:27 +00:00
Quentin McGaw
cade2b99bf chore(routing): unexport IPIsPrivate as ipIsPrivate 2023-06-08 09:14:17 +00:00
Quentin McGaw
40cdb4f662 fix(netlink): RouteList list routes from all tables
- Do not filter by link anymore
- IPv6 detection simplified
2023-06-08 09:12:46 +00:00
Quentin McGaw
c58d6d4de2 chore(lint): upgrade to v1.53.2 and add linters
- gosmopolitan
- mirror
- tagalign
- zerologlint
2023-06-08 07:43:30 +00:00
Quentin McGaw
0da2b6ad0b chore(lint): add musttag linter and fix lint errors
Breaking change: JSON fields changed in the server API
2023-06-08 07:43:26 +00:00
Quentin McGaw
37f0e5c73b chore(lint): add linters dupword, paralleltest and gocheckcompilerdirectives 2023-06-08 07:40:37 +00:00
Quentin McGaw
a9cd7be3f9 chore(sources/env): bump gosettings to v0.3.0-rc13
- Use `RetroKeys` option with env.* method calls
- Use `CSV*` typed methods
- Inject `handleDeprecatedKey` function
2023-06-08 07:40:37 +00:00
Julio Gutierrez
07459ee854 feat(nordvpn): new API endpoint and wireguard support (#1380)
Co-authored-by: Quentin McGaw <quentin.mcgaw@gmail.com>
2023-06-08 09:39:07 +02:00
Quentin McGaw
943943e8d1 fix(settings): MergeWithSlice for both elements nil 2023-06-01 10:00:44 +00:00
Quentin McGaw
5927ee9dec chore(ci): trigger for PR to other branches 2023-06-01 09:09:01 +00:00
Quentin McGaw
3b136e02db chore(secrets): add test for readSecretFileAsStringPtr 2023-06-01 09:07:25 +00:00
Quentin McGaw
482447c151 chore(env): bump qdm12/gosettings to v0.3.0-rc11 2023-06-01 09:07:22 +00:00
Quentin McGaw
5d8fbf8006 fix(sources/secrets): do not lowercase env secret file paths 2023-06-01 08:20:13 +00:00
Quentin McGaw
2ab80771d9 feat(shadowsocks): bump from v0.4.0 to v0.5.0-rc1 2023-05-31 14:31:56 +00:00
Quentin McGaw
7399c00508 chore(sources/env): bump gosettings to v0.3.0-rc9 2023-05-31 14:31:56 +00:00
Leeroy Ding
2d2f657851 docs(readme): fix Alpine version from 3.17 to 3.18 (#1636) 2023-05-31 16:27:10 +02:00
dependabot[bot]
0e21fdc9de Chore(deps): Bump github.com/stretchr/testify from 1.8.3 to 1.8.4 (#1633) 2023-05-31 16:24:49 +02:00
Quentin McGaw
b87b2109b1 chore(settings): use gosettings/sources/env functions 2023-05-30 13:02:10 +00:00
Quentin McGaw
2c30984a10 hotfix(env): read some settings with case sensitivity 2023-05-30 12:46:10 +00:00
Quentin McGaw
47593928f9 fix(settings): use qdm12/gosettings env.Get 2023-05-29 20:43:06 +00:00
Quentin McGaw
b961284845 feat(dev): specify vscode recommendations 2023-05-29 16:42:00 +00:00
Quentin McGaw
b5d230d47a chore(dev): set build tag as linux for cross development 2023-05-29 16:40:10 +00:00
Quentin McGaw
c2972f7bf6 chore(dev): update devcontainer definitions 2023-05-29 15:57:09 +00:00
Quentin McGaw
aed235f52d chore(httpproxy): add Test_returnRedirect to prevent error wrap of ErrUseLastResponse 2023-05-29 09:44:49 +00:00
Quentin McGaw
bfe5e4380f fix(httpproxy): redirect from http to https 2023-05-29 09:39:48 +00:00
Quentin McGaw
eca182a32f chore(tun): not linux or not darwin tagged files 2023-05-29 09:36:29 +00:00
Quentin McGaw
caabaf918e feat(dev): support development on darwin (OSX)
- Netlink linux tagged files
- Netlink linux || darwin tagged files
- Create non-implemented files for NOT linux
- Create non-implemented files for NOT linux and NOT darwin
- Specify wireguard netlink integration test as for linux only
2023-05-29 07:26:59 +00:00
Quentin McGaw
d6924597dd chore(netlink): separate linux only and OS independent code
- Move `Addr` and its `String` method to `types.go`
- Move `IsWireguardSupported` to `wireguard.go` to have `family.go` OS independant
- Remove dependency on vishvananda/netlink in `ipv6.go`
- Move `Link` to `types.go`
- Move `Route` to `types.go`
- Move `Rule` and its `String` method to `types.go`
2023-05-29 06:56:55 +00:00
Quentin McGaw
c26476a2fd chore(netlink): remove unused link fields 2023-05-29 06:56:52 +00:00
Quentin McGaw
5be0d0bbba feat(wireguard): debug logs log obfuscated keys 2023-05-29 06:45:12 +00:00
Quentin McGaw
38ddcfa756 chore(netlink): define own types with minimal fields
- Allow to swap `github.com/vishvananda/netlink`
- Allow to add build tags for each platform
- One step closer to development on non-Linux platforms
2023-05-29 06:44:58 +00:00
Quentin McGaw
163ac48ce4 chore(wireguard): fix netlink integration test
- Broken since recent commit 9d1a0b60a2
2023-05-29 05:54:01 +00:00
Quentin McGaw
def407d610 chore(settings): use qdm12/gosettings functions
- use: FileExists, ObfuscateKey, BoolToYesNo
- remove local functions moved to gosettings
2023-05-28 10:33:36 +00:00
Quentin McGaw
22b2e2cc6e chore(deps): bump qdm12/gosettings to v0.3.0-rc4 2023-05-28 10:29:15 +00:00
Quentin McGaw
c92962e97c chore(deps): tidy Go dependencies 2023-05-28 10:26:25 +00:00
Quentin McGaw
9d1a0b60a2 fix(netlink): use AddrReplace instead of AddrAdd 2023-05-28 10:22:51 +00:00
Quentin McGaw
9cf2c9c4d2 chore(settings): remove now unused helpers/messages.go 2023-05-28 10:22:51 +00:00
Quentin McGaw
e7150ba254 chore(settings): remove unused settings helpers 2023-05-28 10:22:51 +00:00
Filippo Buletto
7ba70f19ef fix(settings): fix httpproxy.go error message (#1596) 2023-05-27 20:01:55 +02:00
dependabot[bot]
9488a9f88a Chore(deps): Bump github.com/breml/rootcerts from 0.2.10 to 0.2.11 (#1567) 2023-05-27 20:01:17 +02:00
dependabot[bot]
020196f1c3 Chore(deps): Bump github.com/stretchr/testify from 1.8.2 to 1.8.3 (#1575) 2023-05-27 20:01:08 +02:00
Quentin McGaw
7e325715c7 hotfix(settings): case insensitivity for server filters 2023-05-27 08:53:18 +00:00
Quentin McGaw
75670a80b8 chore(deps): bump gosettings and govalid 2023-05-27 08:52:41 +00:00
Quentin McGaw
a43973c093 chore(settings): use github.com/qdm12/gosettings 2023-05-25 12:08:43 +00:00
Quentin McGaw
1827a03afd fix(airvpn): allow Airvpn as Wireguard provider 2023-05-24 21:47:31 +00:00
Quentin McGaw
3100cc1e5e hotfix(routing): unmap ipv4-in-ipv6 when converting 2023-05-22 08:03:52 +00:00
Quentin McGaw
eed62fdc6d fix(routing): ip family match function
- ipv4-in-ipv6 should match ipv6
2023-05-22 06:01:52 +00:00
Quentin McGaw
d2b8dbcb10 chore(routing): remove old assigned ip debug log 2023-05-22 06:01:07 +00:00
Quentin McGaw
90d43856ef fix(routing): net.IPNet to netip.Prefix conversion 2023-05-22 06:00:24 +00:00
Quentin McGaw
86f95cb390 chore(docker): bump Alpine from 3.17 to 3.18 2023-05-21 13:25:01 +00:00
Quentin McGaw
3b807e2ca9 feat(openvpn): add support for openvpn 2.6 2023-05-21 13:23:51 +00:00
Quentin McGaw
e8f2296a0d change(openvpn): Openvpn 2.4 no longer supported 2023-05-21 13:20:02 +00:00
Lars Haalck
1dd38bc658 feat(wireguard): WIREGUARD_MTU enviromnent variable (#1571) 2023-05-21 15:11:07 +02:00
354 changed files with 182568 additions and 44994 deletions

View File

@@ -9,17 +9,21 @@ It works on Linux, Windows and OSX.
- [VS code](https://code.visualstudio.com/download) installed
- [VS code remote containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) installed
- [Docker](https://www.docker.com/products/docker-desktop) installed and running
- If you don't use Linux or WSL 2, share your home directory `~/` and the directory of your project with Docker Desktop
- [Docker Compose](https://docs.docker.com/compose/install/) installed
- Ensure your host has the following and that they are accessible by Docker:
- `~/.ssh` directory
- `~/.gitconfig` file (can be empty)
## Setup
1. Create the following files on your host if you don't have them:
```sh
touch ~/.gitconfig ~/.zsh_history
```
Note that the development container will create the empty directories `~/.docker`, `~/.ssh` and `~/.kube` if you don't have them.
1. **For Docker on OSX or Windows without WSL**: ensure your home directory `~` is accessible by Docker.
1. Open the command palette in Visual Studio Code (CTRL+SHIFT+P).
1. Select `Remote-Containers: Open Folder in Container...` and choose the project directory.
1. For Docker running on Windows HyperV, if you want to use SSH keys, bind mount them at `/tmp/.ssh` by changing the `volumes` section in the [docker-compose.yml](docker-compose.yml).
## Customization
@@ -29,13 +33,9 @@ You can make changes to the [Dockerfile](Dockerfile) and then rebuild the image.
```Dockerfile
FROM qmcgaw/godevcontainer
USER root
RUN apk add curl
USER vscode
```
Note that you may need to use `USER root` to build as root, and then change back to `USER vscode`.
To rebuild the image, either:
- With VSCode through the command palette, select `Remote-Containers: Rebuild and reopen in container`
@@ -47,11 +47,11 @@ You can customize **settings** and **extensions** in the [devcontainer.json](dev
### Entrypoint script
You can bind mount a shell script to `/home/vscode/.welcome.sh` to replace the [current welcome script](shell/.welcome.sh).
You can bind mount a shell script to `/root/.welcome.sh` to replace the [current welcome script](https://github.com/qdm12/godevcontainer/blob/master/shell/.welcome.sh).
### Publish a port
To access a port from your host to your development container, publish a port in [docker-compose.yml](docker-compose.yml).
To access a port from your host to your development container, publish a port in [docker-compose.yml](docker-compose.yml). You can also now do it directly with VSCode without restarting the container.
### Run other services

View File

@@ -1,82 +1,73 @@
{
"name": "gluetun-dev",
"dockerComposeFile": [
"docker-compose.yml"
],
"service": "vscode",
"runServices": [
"vscode"
],
"shutdownAction": "stopCompose",
"postCreateCommand": "source ~/.windows.sh && go mod download && go mod tidy",
"workspaceFolder": "/workspace",
"extensions": [
"golang.go",
"eamodio.gitlens", // IDE Git information
"davidanson.vscode-markdownlint",
"ms-azuretools.vscode-docker", // Docker integration and linting
"shardulm94.trailing-spaces", // Show trailing spaces
"Gruntfuggly.todo-tree", // Highlights TODO comments
"bierner.emojisense", // Emoji sense for markdown
"stkb.rewrap", // rewrap comments after n characters on one line
"vscode-icons-team.vscode-icons", // Better file extension icons
"github.vscode-pull-request-github", // Github interaction
"redhat.vscode-yaml", // Kubernetes, Drone syntax highlighting
"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",
"remote.extensionKind": {
"ms-azuretools.vscode-docker": "workspace"
},
"editor.codeActionsOnSaveTimeout": 3000,
"go.useLanguageServer": true,
"[go]": {
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.organizeImports": true,
},
// Optional: Disable snippets, as they conflict with completion ranking.
"editor.snippetSuggestions": "none"
},
"[go.mod]": {
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.organizeImports": true,
},
},
"gopls": {
"usePlaceholders": false,
"staticcheck": true
},
"go.autocompleteUnimportedPackages": true,
"go.gotoSymbol.includeImports": true,
"go.gotoSymbol.includeGoroot": true,
"go.lintTool": "golangci-lint",
"go.buildOnSave": "workspace",
"go.lintOnSave": "workspace",
"go.vetOnSave": "workspace",
"editor.formatOnSave": true,
"go.toolsEnvVars": {
"GOFLAGS": "-tags=",
// "CGO_ENABLED": 1 // for the race detector
},
"gopls.env": {
"GOFLAGS": "-tags="
},
"go.testEnvVars": {
"": ""
},
"go.testFlags": [
"-v",
// "-race"
],
"go.testTimeout": "10s",
"go.coverOnSingleTest": true,
"go.coverOnSingleTestFile": true,
"go.coverOnTestPackage": true
}
{
"name": "gluetun-dev",
"dockerComposeFile": [
"docker-compose.yml"
],
"service": "vscode",
"runServices": [
"vscode"
],
"shutdownAction": "stopCompose",
"postCreateCommand": "~/.windows.sh && go mod download && go mod tidy",
"workspaceFolder": "/workspace",
// "overrideCommand": "",
"customizations": {
"vscode": {
"extensions": [
"golang.go",
"eamodio.gitlens", // IDE Git information
"davidanson.vscode-markdownlint",
"ms-azuretools.vscode-docker", // Docker integration and linting
"shardulm94.trailing-spaces", // Show trailing spaces
"Gruntfuggly.todo-tree", // Highlights TODO comments
"bierner.emojisense", // Emoji sense for markdown
"stkb.rewrap", // rewrap comments after n characters on one line
"vscode-icons-team.vscode-icons", // Better file extension icons
"github.vscode-pull-request-github", // Github interaction
"redhat.vscode-yaml", // Kubernetes, Drone syntax highlighting
"bajdzis.vscode-database", // Supports connections to mysql or postgres, over SSL, socked
"IBM.output-colorizer", // Colorize your output/test logs
"github.copilot" // AI code completion
],
"settings": {
"files.eol": "\n",
"remote.extensionKind": {
"ms-azuretools.vscode-docker": "workspace"
},
"go.useLanguageServer": true,
"[go]": {
"editor.codeActionsOnSave": {
"source.organizeImports": true
}
},
"[go.mod]": {
"editor.codeActionsOnSave": {
"source.organizeImports": true
}
},
"gopls": {
"usePlaceholders": false,
"staticcheck": true
},
"go.lintTool": "golangci-lint",
"go.lintOnSave": "package",
"editor.formatOnSave": true,
"go.buildTags": "linux",
"go.toolsEnvVars": {
"CGO_ENABLED": "0"
},
"go.testEnvVars": {
"CGO_ENABLED": "1"
},
"go.testFlags": [
"-v",
"-race"
],
"go.testTimeout": "10s",
"go.coverOnSingleTest": true,
"go.coverOnSingleTestFile": true,
"go.coverOnTestPackage": true
}
}
}
}

View File

@@ -1,30 +1,28 @@
version: "3.7"
services:
vscode:
build: .
devices:
- /dev/net/tun:/dev/net/tun
volumes:
- ../:/workspace
# Docker socket to access Docker server
- /var/run/docker.sock:/var/run/docker.sock
# Docker configuration
- ~/.docker:/root/.docker
# SSH directory for Linux, OSX and WSL
# On Linux and OSX, a symlink /mnt/ssh <-> ~/.ssh is
# created in the container. On Windows, files are copied
# from /mnt/ssh to ~/.ssh to fix permissions.
- ~/.ssh:/mnt/ssh
# Shell history persistence
- ~/.zsh_history:/root/.zsh_history
environment:
- TZ=
cap_add:
# For debugging with dlv
# - SYS_PTRACE
- NET_ADMIN
security_opt:
# For debugging with dlv
- seccomp:unconfined
entrypoint: zsh -c "while sleep 1000; do :; done"
version: "3.7"
services:
vscode:
build: .
volumes:
- ../:/workspace
# Docker socket to access Docker server
- /var/run/docker.sock:/var/run/docker.sock
# SSH directory for Linux, OSX and WSL
# On Linux and OSX, a symlink /mnt/ssh <-> ~/.ssh is
# created in the container. On Windows, files are copied
# from /mnt/ssh to ~/.ssh to fix permissions.
- ~/.ssh:/mnt/ssh
# Shell history persistence
- ~/.zsh_history:/root/.zsh_history
# Git config
- ~/.gitconfig:/root/.gitconfig
environment:
- TZ=
cap_add:
# For debugging with dlv
- SYS_PTRACE
- NET_ADMIN
security_opt:
# For debugging with dlv
- seccomp:unconfined
entrypoint: [ "zsh", "-c", "while sleep 1000; do :; done" ]

View File

@@ -13,6 +13,6 @@ Contributions are [released](https://help.github.com/articles/github-terms-of-se
## Resources
- [Gluetun guide on development](https://github.com/qdm12/gluetun/wiki/Development)
- [Gluetun guide on development](https://github.com/qdm12/gluetun-wiki/blob/main/contributing/development.md)
- [Using Pull Requests](https://help.github.com/articles/about-pull-requests/)
- [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/)

View File

@@ -7,13 +7,18 @@ body:
attributes:
value: |
Thanks for taking the time to fill out this bug report!
⚠️ Your issue will be instantly closed as not planned WITHOUT explanation if:
- you do not fill out **the title of the issue** ☝️
- you do not provide the **Gluetun version** as requested below
- you provide **less than 10 lines of logs** as requested below
- type: dropdown
id: urgent
attributes:
label: Is this urgent?
description: |
Is this a critical bug, or do you need this fixed urgently?
If this is urgent, note you can use one of the [image tags available](https://github.com/qdm12/gluetun/wiki/Docker-image-tags) if that can help.
If this is urgent, note you can use one of the [image tags available](https://github.com/qdm12/gluetun-wiki/blob/main/setup/docker-image-tags.md) if that can help.
options:
- "No"
- "Yes"
@@ -75,6 +80,7 @@ body:
- Portainer
- Kubernetes
- Podman
- Unraid
- Other
validations:
required: true
@@ -84,7 +90,7 @@ body:
label: What is the version of Gluetun
description: |
Copy paste the version line at the top of your logs.
It should be in the form `Running version latest built on 2020-03-13T01:30:06Z (commit d0f678c)`.
It MUST be in the form `Running version latest built on 2020-03-13T01:30:06Z (commit d0f678c)`.
validations:
required: true
- type: textarea
@@ -97,7 +103,7 @@ body:
- type: textarea
id: logs
attributes:
label: Share your logs
label: Share your logs (at least 10 lines)
description: No sensitive information is logged out except when running with `LOG_LEVEL=debug`.
render: plain text
validations:

View File

@@ -1,4 +1,7 @@
contact_links:
- name: Report a Wiki issue
url: https://github.com/qdm12/gluetun-wiki/issues/new
about: Please create an issue on the gluetun-wiki repository.
- name: Configuration help?
url: https://github.com/qdm12/gluetun/discussions/new
about: Please create a Github discussion.

View File

@@ -14,4 +14,4 @@ One of the following is required:
If the list of servers requires to login **or** is hidden behind an interactive configurator,
you can only use a custom Openvpn configuration file.
[The Wiki](https://github.com/qdm12/gluetun/wiki/Openvpn-file) describes how to do so.
[The Wiki's OpenVPN configuration file page](https://github.com/qdm12/gluetun-wiki/blob/main/setup/openvpn-configuration-file.md) describes how to do so.

View File

@@ -1,18 +0,0 @@
name: Wiki issue
description: Report a Wiki issue
title: "Wiki issue: "
labels: ["📄 Wiki issue"]
body:
- type: input
id: url
attributes:
label: "URL to the Wiki page"
placeholder: "https://github.com/qdm12/gluetun/wiki/OpenVPN-options"
validations:
required: true
- type: textarea
id: description
attributes:
label: "What's the issue?"
validations:
required: true

12
.github/labels.yml vendored
View File

@@ -95,6 +95,9 @@
description: ""
# Problem category
- name: "Config problem"
color: "ffc7ea"
description: ""
- name: "Openvpn"
color: "ffc7ea"
description: ""
@@ -107,6 +110,15 @@
- name: "Firewall"
color: "ffc7ea"
description: ""
- name: "Routing"
color: "ffc7ea"
description: ""
- name: "IPv6"
color: "ffc7ea"
description: ""
- name: "Port forwarding"
color: "ffc7ea"
description: ""
- name: "HTTP proxy"
color: "ffc7ea"
description: ""

View File

@@ -14,8 +14,6 @@ on:
- go.mod
- go.sum
pull_request:
branches:
- master
paths-ignore:
- .github/workflows/ci.yml
- cmd/**

View File

@@ -17,8 +17,6 @@ on:
- go.mod
- go.sum
pull_request:
branches:
- master
paths:
- .github/workflows/ci.yml
- cmd/**
@@ -39,7 +37,7 @@ jobs:
env:
DOCKER_BUILDKIT: "1"
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: reviewdog/action-misspell@v1
with:
@@ -47,6 +45,7 @@ jobs:
level: error
exclude: |
./internal/storage/servers.json
*.md
- name: Linting
run: docker build --target lint .
@@ -74,12 +73,12 @@ jobs:
contents: read
security-events: write
steps:
- uses: actions/checkout@v3
- uses: github/codeql-action/init@v2
- uses: actions/checkout@v4
- uses: github/codeql-action/init@v3
with:
languages: go
- uses: github/codeql-action/autobuild@v2
- uses: github/codeql-action/analyze@v2
- uses: github/codeql-action/autobuild@v3
- uses: github/codeql-action/analyze@v3
publish:
if: |
@@ -96,13 +95,13 @@ jobs:
packages: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
# extract metadata (tags, labels) for Docker
# https://github.com/docker/metadata-action
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@v4
uses: docker/metadata-action@v5
with:
flavor: |
latest=${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }}
@@ -117,15 +116,15 @@ jobs:
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/setup-qemu-action@v3
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v2
- uses: docker/login-action@v3
with:
username: qmcgaw
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- uses: docker/login-action@v2
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: qdm12
@@ -136,7 +135,7 @@ jobs:
run: echo "::set-output name=value::$(git rev-parse --short HEAD)"
- name: Build and push final image
uses: docker/build-push-action@v4.0.0
uses: docker/build-push-action@v5
with:
platforms: linux/amd64,linux/386,linux/arm64,linux/arm/v6,linux/arm/v7,linux/ppc64le
labels: ${{ steps.meta.outputs.labels }}

21
.github/workflows/closed-issue.yml vendored Normal file
View File

@@ -0,0 +1,21 @@
name: Closed issue
on:
issues:
types: [closed]
jobs:
comment:
permissions:
issues: write
runs-on: ubuntu-latest
steps:
- uses: peter-evans/create-or-update-comment@v4
with:
token: ${{ github.token }}
issue-number: ${{ github.event.issue.number }}
body: |
Closed issues are **NOT** monitored, so commenting here is likely to be not seen.
If you think this is *still unresolved* and have **more information** to bring, please create another issue.
This is an automated comment setup because @qdm12 is the sole maintainer of this project
which became too popular to monitor issues closed.

View File

@@ -1,25 +0,0 @@
name: Docker Hub description
on:
push:
branches:
- master
paths:
- README.md
- .github/workflows/dockerhub-description.yml
jobs:
docker-hub-description:
if: github.repository == 'qdm12/gluetun'
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
steps:
- uses: actions/checkout@v3
- uses: peter-evans/dockerhub-description@v3
with:
username: qmcgaw
password: ${{ secrets.DOCKERHUB_PASSWORD }}
repository: qmcgaw/gluetun
short-description: Lightweight Swiss-knife VPN client to connect to several VPN providers
readme-filepath: README.md

View File

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

21
.github/workflows/markdown-skip.yml vendored Normal file
View File

@@ -0,0 +1,21 @@
name: Markdown
on:
push:
branches:
- master
paths-ignore:
- "**.md"
- .github/workflows/markdown.yml
pull_request:
paths-ignore:
- "**.md"
- .github/workflows/markdown.yml
jobs:
markdown:
runs-on: ubuntu-latest
permissions:
actions: read
steps:
- name: No trigger path triggered for required markdown workflow.
run: exit 0

46
.github/workflows/markdown.yml vendored Normal file
View File

@@ -0,0 +1,46 @@
name: Markdown
on:
push:
branches:
- master
paths:
- "**.md"
- .github/workflows/markdown.yml
pull_request:
paths:
- "**.md"
- .github/workflows/markdown.yml
jobs:
markdown:
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
steps:
- uses: actions/checkout@v4
- uses: DavidAnson/markdownlint-cli2-action@v14
with:
globs: "**.md"
config: .markdownlint.json
- uses: reviewdog/action-misspell@v1
with:
locale: "US"
level: error
pattern: |
*.md
- uses: gaurav-nelson/github-action-markdown-link-check@v1
with:
use-quiet-mode: yes
- uses: peter-evans/dockerhub-description@v3
if: github.repository == 'qdm12/gluetun' && github.event_name == 'push'
with:
username: qmcgaw
password: ${{ secrets.DOCKERHUB_PASSWORD }}
repository: qmcgaw/gluetun
short-description: Lightweight Swiss-knife VPN client to connect to several VPN providers
readme-filepath: README.md

22
.github/workflows/opened-issue.yml vendored Normal file
View File

@@ -0,0 +1,22 @@
name: Opened issue
on:
issues:
types: [opened]
jobs:
comment:
permissions:
issues: write
runs-on: ubuntu-latest
steps:
- uses: peter-evans/create-or-update-comment@v4
with:
token: ${{ github.token }}
issue-number: ${{ github.event.issue.number }}
body: |
@qdm12 is more or less the only maintainer of this project and works on it in his free time.
Please:
- **do not** ask for updates, be patient
- :+1: the issue to show your support instead of commenting
@qdm12 usually checks issues at least once a week, if this is a new urgent bug,
[revert to an older tagged container image](https://github.com/qdm12/gluetun-wiki/blob/main/setup/docker-image-tags.md)

View File

@@ -9,6 +9,8 @@ issues:
- dupl
- goerr113
- containedctx
- goconst
- maintidx
- path: "internal\\/server\\/.+\\.go"
linters:
- dupl
@@ -33,6 +35,18 @@ issues:
text: "newCipherDESCBCBlock returns interface \\(github\\.com\\/youmark\\/pkcs8\\.Cipher\\)"
linters:
- ireturn
- path: "internal\\/firewall\\/.*\\.go"
text: "string `-i ` has [1-9][0-9]* occurrences, make it a constant"
linters:
- goconst
- path: "internal\\/provider\\/ipvanish\\/updater\\/servers.go"
text: "string ` in ` has 3 occurrences, make it a constant"
linters:
- goconst
- path: "internal\\/vpn\\/portforward.go"
text: 'directive `//nolint:ireturn` is unused for linter "ireturn"'
linters:
- nolintlint
linters:
enable:
@@ -46,6 +60,7 @@ linters:
- decorder
- dogsled
- dupl
- dupword
- durationcheck
- errchkjson
- errname
@@ -54,6 +69,7 @@ linters:
- exportloopref
- forcetypeassert
- gci
- gocheckcompilerdirectives
- gochecknoglobals
- gochecknoinits
- gocognit
@@ -68,6 +84,7 @@ linters:
- gomoddirectives
- goprintffuncname
- gosec
- gosmopolitan
- grouper
- importas
- interfacebloat
@@ -75,7 +92,9 @@ linters:
- lll
- maintidx
- makezero
- mirror
- misspell
- musttag
- nakedret
- nestif
- nilerr
@@ -83,6 +102,7 @@ linters:
- noctx
- nolintlint
- nosprintfhostport
- paralleltest
- prealloc
- predeclared
- promlinter
@@ -90,6 +110,7 @@ linters:
- revive
- rowserrcheck
- sqlclosecheck
- tagalign
- tenv
- thelper
- tparallel
@@ -98,6 +119,7 @@ linters:
- usestdlibvars
- wastedassign
- whitespace
- zerologlint
run:
skip-dirs:

3
.markdownlint.json Normal file
View File

@@ -0,0 +1,3 @@
{
"MD013": false
}

8
.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,8 @@
{
// This list should be kept to the strict minimum
// to develop this project.
"recommendations": [
"golang.go",
"davidanson.vscode-markdownlint",
],
}

29
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,29 @@
{
// The settings should be kept to the strict minimum
// to develop this project.
"files.eol": "\n",
"editor.formatOnSave": true,
"go.buildTags": "linux",
"go.toolsEnvVars": {
"CGO_ENABLED": "0"
},
"go.testEnvVars": {
"CGO_ENABLED": "1"
},
"go.testFlags": [
"-v",
"-race"
],
"go.testTimeout": "10s",
"go.coverOnSingleTest": true,
"go.coverOnSingleTestFile": true,
"go.coverOnTestPackage": true,
"go.useLanguageServer": true,
"[go]": {
"editor.codeActionsOnSave": {
"source.organizeImports": true
}
},
"go.lintTool": "golangci-lint",
"go.lintOnSave": "package"
}

View File

@@ -1,8 +1,8 @@
ARG ALPINE_VERSION=3.17
ARG GO_ALPINE_VERSION=3.17
ARG GO_VERSION=1.20
ARG ALPINE_VERSION=3.18
ARG GO_ALPINE_VERSION=3.18
ARG GO_VERSION=1.21
ARG XCPUTRANSLATE_VERSION=v0.6.0
ARG GOLANGCI_LINT_VERSION=v1.52.2
ARG GOLANGCI_LINT_VERSION=v1.56.2
ARG MOCKGEN_VERSION=v1.6.0
ARG BUILDPLATFORM=linux/amd64
@@ -90,25 +90,34 @@ ENV VPN_SERVICE_PROVIDER=pia \
OPENVPN_FLAGS= \
OPENVPN_CIPHERS= \
OPENVPN_AUTH= \
OPENVPN_PROCESS_USER= \
OPENVPN_PROCESS_USER=root \
OPENVPN_CUSTOM_CONFIG= \
# Wireguard
WIREGUARD_CONF_SECRETFILE=/run/secrets/wg0.conf \
WIREGUARD_PRIVATE_KEY= \
WIREGUARD_PRIVATE_KEY_SECRETFILE=/run/secrets/wireguard_private_key \
WIREGUARD_PRESHARED_KEY= \
WIREGUARD_PRESHARED_KEY_SECRETFILE=/run/secrets/wireguard_preshared_key \
WIREGUARD_PUBLIC_KEY= \
WIREGUARD_ALLOWED_IPS= \
WIREGUARD_ADDRESSES= \
WIREGUARD_ADDRESSES_SECRETFILE=/run/secrets/wireguard_addresses \
WIREGUARD_MTU=1400 \
WIREGUARD_IMPLEMENTATION=auto \
# VPN server filtering
SERVER_REGIONS= \
SERVER_COUNTRIES= \
SERVER_CITIES= \
SERVER_HOSTNAMES= \
SERVER_CATEGORIES= \
# # Mullvad only:
ISP= \
OWNED_ONLY=no \
# # Private Internet Access only:
PRIVATE_INTERNET_ACCESS_OPENVPN_ENCRYPTION_PRESET= \
VPN_PORT_FORWARDING=off \
VPN_PORT_FORWARDING_LISTENING_PORT=0 \
VPN_PORT_FORWARDING_PROVIDER= \
VPN_PORT_FORWARDING_STATUS_FILE="/tmp/gluetun/forwarded_port" \
# # Cyberghost only:
OPENVPN_CERT= \
@@ -130,6 +139,8 @@ ENV VPN_SERVICE_PROVIDER=pia \
MULTIHOP_ONLY= \
# # VPN Secure only:
PREMIUM_ONLY= \
# # PIA only:
PORT_FORWARD_ONLY= \
# Firewall
FIREWALL=on \
FIREWALL_VPN_INPUT_PORTS= \
@@ -164,6 +175,7 @@ ENV VPN_SERVICE_PROVIDER=pia \
HTTPPROXY= \
HTTPPROXY_LOG=off \
HTTPPROXY_LISTENING_ADDRESS=":8888" \
HTTPPROXY_STEALTH=off \
HTTPPROXY_USER= \
HTTPPROXY_PASSWORD= \
HTTPPROXY_USER_SECRETFILE=/run/secrets/httpproxy_user \
@@ -176,6 +188,7 @@ ENV VPN_SERVICE_PROVIDER=pia \
SHADOWSOCKS_PASSWORD_SECRETFILE=/run/secrets/shadowsocks_password \
SHADOWSOCKS_CIPHER=chacha20-ietf-poly1305 \
# Control server
HTTP_CONTROL_SERVER_LOG=on \
HTTP_CONTROL_SERVER_ADDRESS=":8000" \
# Server data updater
UPDATER_PERIOD=0 \
@@ -184,6 +197,8 @@ ENV VPN_SERVICE_PROVIDER=pia \
# Public IP
PUBLICIP_FILE="/tmp/gluetun/ip" \
PUBLICIP_PERIOD=12h \
PUBLICIP_API=ipinfo \
PUBLICIP_API_TOKEN= \
# Pprof
PPROF_ENABLED=no \
PPROF_BLOCK_PROFILE_RATE=0 \
@@ -199,12 +214,11 @@ 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 wget && \
apk add --no-cache --update -X "https://dl-cdn.alpinelinux.org/alpine/v3.12/main" openvpn==2.4.12-r0 && \
apk add --no-cache --update -X "https://dl-cdn.alpinelinux.org/alpine/v3.16/main" openssl\~1.1 && \
mv /usr/sbin/openvpn /usr/sbin/openvpn2.4 && \
apk add --no-cache --update -X "https://dl-cdn.alpinelinux.org/alpine/v3.17/main" openvpn\~2.5 && \
mv /usr/sbin/openvpn /usr/sbin/openvpn2.5 && \
apk del openvpn && \
apk add --no-cache --update openvpn ca-certificates iptables ip6tables unbound tzdata && \
mv /usr/sbin/openvpn /usr/sbin/openvpn2.5 && \
mv /usr/sbin/openvpn /usr/sbin/openvpn2.6 && \
# Fix vulnerability issue
apk add --no-cache --update busybox && \
rm -rf /var/cache/apk/* /etc/unbound/* /usr/sbin/unbound-* /etc/openvpn/*.sh /usr/lib/openvpn/plugins/openvpn-plugin-down-root.so && \

View File

@@ -35,20 +35,19 @@ Lightweight swiss-knife-like VPN client to multiple VPN service providers
## Quick links
- [Setup](#Setup)
- [Features](#Features)
- [Setup](#setup)
- [Features](#features)
- Problem?
- [Check the Wiki](https://github.com/qdm12/gluetun/wiki)
- Check the Wiki [common errors](https://github.com/qdm12/gluetun-wiki/tree/main/errors) and [faq](https://github.com/qdm12/gluetun-wiki/tree/main/faq)
- [Start a discussion](https://github.com/qdm12/gluetun/discussions)
- [Fix the Unraid template](https://github.com/qdm12/gluetun/discussions/550)
- Suggestion?
- [Create an issue](https://github.com/qdm12/gluetun/issues)
- [Join the Slack channel](https://join.slack.com/t/qdm12/shared_invite/enQtOTE0NjcxNTM1ODc5LTYyZmVlOTM3MGI4ZWU0YmJkMjUxNmQ4ODQ2OTAwYzMxMTlhY2Q1MWQyOWUyNjc2ODliNjFjMDUxNWNmNzk5MDk)
- Happy?
- 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)
- **Want to add a VPN provider?** check [the development page](https://github.com/qdm12/gluetun-wiki/blob/main/contributing/development.md) and [add a provider page](https://github.com/qdm12/gluetun-wiki/blob/main/contributing/add-a-provider.md)
- Video:
[![Video Gif](https://i.imgur.com/CetWunc.gif)](https://youtu.be/0F6I03LQcI4)
@@ -57,13 +56,13 @@ Lightweight swiss-knife-like VPN client to multiple VPN service providers
## Features
- Based on Alpine 3.17 for a small Docker image of 42MB
- Based on Alpine 3.18 for a small Docker image of 35.6MB
- Supports: **AirVPN**, **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**, **Surfshark** and **Windscribe**
- For **ProtonVPN**, **PureVPN**, **Torguard**, **VPN Unlimited** and **WeVPN** using [the custom provider](https://github.com/qdm12/gluetun/wiki/Custom-provider)
- For custom Wireguard configurations using [the custom provider](https://github.com/qdm12/gluetun/wiki/Custom-provider)
- For **AirVPN**, **Ivpn**, **Mullvad**, **NordVPN**, **Surfshark** and **Windscribe**
- For **ProtonVPN**, **PureVPN**, **Torguard**, **VPN Unlimited** and **WeVPN** using [the custom provider](https://github.com/qdm12/gluetun-wiki/blob/main/setup/providers/custom.md)
- For custom Wireguard configurations using [the custom provider](https://github.com/qdm12/gluetun-wiki/blob/main/setup/providers/custom.md)
- More in progress, see [#134](https://github.com/qdm12/gluetun/issues/134)
- DNS over TLS baked in with service provider(s) of your choice
- DNS fine blocking of malicious/ads/surveillance hostnames and IP addresses, with live update every 24 hours
@@ -71,10 +70,10 @@ Lightweight swiss-knife-like VPN client to multiple VPN service providers
- Built in firewall kill switch to allow traffic only with needed the VPN servers and LAN devices
- Built in Shadowsocks proxy (protocol based on SOCKS5 with an encryption layer, tunnels TCP+UDP)
- Built in HTTP proxy (tunnels HTTP and HTTPS through TCP)
- [Connect other containers to it](https://github.com/qdm12/gluetun/wiki/Connect-a-container-to-gluetun)
- [Connect LAN devices to it](https://github.com/qdm12/gluetun/wiki/Connect-a-LAN-device-to-gluetun)
- [Connect other containers to it](https://github.com/qdm12/gluetun-wiki/blob/main/setup/connect-a-container-to-gluetun.md)
- [Connect LAN devices to it](https://github.com/qdm12/gluetun-wiki/blob/main/setup/connect-a-lan-device-to-gluetun.md)
- Compatible with amd64, i686 (32 bit), **ARM** 64 bit, ARM 32 bit v6 and v7, and even ppc64le 🎆
- [Custom VPN server side port forwarding for Private Internet Access](https://github.com/qdm12/gluetun/wiki/Private-internet-access#vpn-server-port-forwarding)
- [Custom VPN server side port forwarding for Private Internet Access](https://github.com/qdm12/gluetun-wiki/blob/main/setup/providers/private-internet-access.md#vpn-server-port-forwarding)
- Possibility of split horizon DNS by selecting multiple DNS over TLS providers
- Unbound subprogram drops root privileges once launched
- Can work as a Kubernetes sidecar container, thanks @rorph
@@ -83,9 +82,9 @@ Lightweight swiss-knife-like VPN client to multiple VPN service providers
🎉 There are now instructions specific to each VPN provider with examples to help you get started as quickly as possible!
Go to the [Wiki](https://github.com/qdm12/gluetun/wiki)!
Go to the [Wiki](https://github.com/qdm12/gluetun-wiki)!
[🐛 Found a bug in the Wiki?!](https://github.com/qdm12/gluetun/issues/new?assignees=&labels=%F0%9F%93%84+Wiki+issue&template=wiki+issue.yml&title=Wiki+issue%3A+)
[🐛 Found a bug in the Wiki?!](https://github.com/qdm12/gluetun-wiki/issues/new)
Here's a docker-compose.yml for the laziest:
@@ -95,7 +94,8 @@ services:
gluetun:
image: qmcgaw/gluetun
# container_name: gluetun
# 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
# line above must be uncommented to allow external containers to connect.
# See https://github.com/qdm12/gluetun-wiki/blob/main/setup/connect-a-container-to-gluetun.md#external-container-to-gluetun
cap_add:
- NET_ADMIN
devices:
@@ -107,7 +107,7 @@ services:
volumes:
- /yourpath:/gluetun
environment:
# See https://github.com/qdm12/gluetun/wiki
# See https://github.com/qdm12/gluetun-wiki/tree/main/setup#setup
- VPN_SERVICE_PROVIDER=ivpn
- VPN_TYPE=openvpn
# OpenVPN:
@@ -118,13 +118,13 @@ services:
# - WIREGUARD_ADDRESSES=10.64.222.21/32
# Timezone for accurate log times
- TZ=
# Server list updater. See https://github.com/qdm12/gluetun/wiki/Updating-Servers#periodic-update
# Server list updater
# See https://github.com/qdm12/gluetun-wiki/blob/main/setup/servers.md#update-the-vpn-servers-list
- UPDATER_PERIOD=
- UPDATER_VPN_SERVICE_PROVIDERS=
```
🆕 Image also available as `ghcr.io/qdm12/gluetun`
## License
[![MIT](https://img.shields.io/github/license/qdm12/gluetun)](https://github.com/qdm12/gluetun/master/LICENSE)
[![MIT](https://img.shields.io/github/license/qdm12/gluetun)](https://github.com/qdm12/gluetun/blob/master/LICENSE)

View File

@@ -34,7 +34,7 @@ import (
"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"
pubipapi "github.com/qdm12/gluetun/internal/publicip/api"
"github.com/qdm12/gluetun/internal/routing"
"github.com/qdm12/gluetun/internal/server"
"github.com/qdm12/gluetun/internal/shadowsocks"
@@ -82,10 +82,10 @@ func main() {
cli := cli.New()
cmder := command.NewCmder()
envReader := env.New(logger)
filesReader := files.New()
secretsReader := secrets.New()
muxReader := mux.New(envReader, filesReader, secretsReader)
filesReader := files.New()
envReader := env.New(logger)
muxReader := mux.New(secretsReader, filesReader, envReader)
errorCh := make(chan error)
go func() {
@@ -159,7 +159,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
}
}
announcementExp, err := time.Parse(time.RFC3339, "2021-02-15T00:00:00Z")
announcementExp, err := time.Parse(time.RFC3339, "2023-07-01T00:00:00Z")
if err != nil {
return err
}
@@ -170,7 +170,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
Version: buildInfo.Version,
Commit: buildInfo.Commit,
BuildDate: buildInfo.Created,
Announcement: "Large settings parsing refactoring merged on 2022-01-06, please report any issue!",
Announcement: "Wiki moved to https://github.com/qdm12/gluetun-wiki",
AnnounceExp: announcementExp,
// Sponsor information
PaypalUser: "qmcgaw",
@@ -264,8 +264,8 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
err = printVersions(ctx, logger, []printVersionElement{
{name: "Alpine", getVersion: alpineConf.Version},
{name: "OpenVPN 2.4", getVersion: ovpnConf.Version24},
{name: "OpenVPN 2.5", getVersion: ovpnConf.Version25},
{name: "OpenVPN 2.6", getVersion: ovpnConf.Version26},
{name: "Unbound", getVersion: dnsConf.Version},
{name: "IPtables", getVersion: func(ctx context.Context) (version string, err error) {
return firewall.Version(ctx, cmder)
@@ -331,11 +331,15 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
}
const tunDevice = "/dev/net/tun"
if err := tun.Check(tunDevice); err != nil {
err = tun.Check(tunDevice)
if err != nil {
if !errors.Is(err, os.ErrNotExist) {
return fmt.Errorf("checking TUN device: %w (see the Wiki errors/tun page)", err)
}
logger.Info(err.Error() + "; creating it...")
err = tun.Create(tunDevice)
if err != nil {
return err
return fmt.Errorf("creating tun device: %w", err)
}
}
@@ -376,12 +380,13 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
portForwardLogger := logger.New(log.SetComponent("port forwarding"))
portForwardLooper := portforward.NewLoop(allSettings.VPN.Provider.PortForwarding,
httpClient, firewallConf, portForwardLogger, puid, pgid)
portForwardHandler, portForwardCtx, portForwardDone := goshutdown.NewGoRoutineHandler(
"port forwarding", goroutine.OptionTimeout(time.Second))
go portForwardLooper.Run(portForwardCtx, portForwardDone)
routingConf, httpClient, firewallConf, portForwardLogger, puid, pgid)
portForwardRunError, err := portForwardLooper.Start(ctx)
if err != nil {
return fmt.Errorf("starting port forwarding loop: %w", err)
}
unboundLogger := logger.New(log.SetComponent("dns over tls"))
unboundLogger := logger.New(log.SetComponent("dns"))
unboundLooper := dns.NewLoop(dnsConf, allSettings.DNS, httpClient,
unboundLogger)
dnsHandler, dnsCtx, dnsDone := goshutdown.NewGoRoutineHandler(
@@ -395,19 +400,18 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
go unboundLooper.RunRestartTicker(dnsTickerCtx, dnsTickerDone)
controlGroupHandler.Add(dnsTickerHandler)
ipFetcher := ipinfo.New(httpClient)
publicipAPI, _ := pubipapi.ParseProvider(allSettings.PublicIP.API)
ipFetcher, err := pubipapi.New(publicipAPI, httpClient, *allSettings.PublicIP.APIToken)
if err != nil {
return fmt.Errorf("creating public IP API client: %w", err)
}
publicIPLooper := publicip.NewLoop(ipFetcher,
logger.New(log.SetComponent("ip getter")),
allSettings.PublicIP, puid, pgid)
pubIPHandler, pubIPCtx, pubIPDone := goshutdown.NewGoRoutineHandler(
"public IP", goroutine.OptionTimeout(defaultShutdownTimeout))
go publicIPLooper.Run(pubIPCtx, pubIPDone)
otherGroupHandler.Add(pubIPHandler)
pubIPTickerHandler, pubIPTickerCtx, pubIPTickerDone := goshutdown.NewGoRoutineHandler(
"public IP", goroutine.OptionTimeout(defaultShutdownTimeout))
go publicIPLooper.RunRestartTicker(pubIPTickerCtx, pubIPTickerDone)
tickersGroupHandler.Add(pubIPTickerHandler)
publicIPRunError, err := publicIPLooper.Start(ctx)
if err != nil {
return fmt.Errorf("starting public ip loop: %w", err)
}
updaterLogger := logger.New(log.SetComponent("updater"))
@@ -481,13 +485,31 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
order.OptionOnSuccess(defaultShutdownOnSuccess),
order.OptionOnFailure(defaultShutdownOnFailure))
orderHandler.Append(controlGroupHandler, tickersGroupHandler, healthServerHandler,
vpnHandler, portForwardHandler, otherGroupHandler)
vpnHandler, otherGroupHandler)
// Start VPN for the first time in a blocking call
// until the VPN is launched
_, _ = vpnLooper.ApplyStatus(ctx, constants.Running) // TODO option to disable with variable
<-ctx.Done()
select {
case <-ctx.Done():
stoppers := []interface {
String() string
Stop() error
}{
portForwardLooper, publicIPLooper,
}
for _, stopper := range stoppers {
err := stopper.Stop()
if err != nil {
logger.Error(fmt.Sprintf("stopping %s: %s", stopper, err))
}
}
case err := <-portForwardRunError:
logger.Errorf("port forwarding loop crashed: %s", err)
case err := <-publicIPRunError:
logger.Errorf("public IP loop crashed: %s", err)
}
return orderHandler.Shutdown(context.Background())
}
@@ -531,30 +553,29 @@ type netLinker interface {
type Addresser interface {
AddrList(link netlink.Link, family int) (
addresses []netlink.Addr, err error)
AddrAdd(link netlink.Link, addr *netlink.Addr) error
AddrReplace(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
RouteList(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
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)
LinkAdd(link netlink.Link) (linkIndex int, err error)
LinkDel(link netlink.Link) (err error)
LinkSetUp(link netlink.Link) (err error)
LinkSetUp(link netlink.Link) (linkIndex int, err error)
LinkSetDown(link netlink.Link) (err error)
}

31
go.mod
View File

@@ -1,29 +1,33 @@
module github.com/qdm12/gluetun
go 1.20
go 1.21
require (
github.com/breml/rootcerts v0.2.10
github.com/fatih/color v1.15.0
github.com/breml/rootcerts v0.2.17
github.com/fatih/color v1.16.0
github.com/golang/mock v1.6.0
github.com/klauspost/compress v1.17.4
github.com/klauspost/pgzip v1.2.6
github.com/qdm12/dns v1.11.0
github.com/qdm12/golibs v0.0.0-20210822203818-5c568b0777b6
github.com/qdm12/gosettings v0.4.0-rc1
github.com/qdm12/goshutdown v0.3.0
github.com/qdm12/gosplash v0.1.0
github.com/qdm12/gotree v0.2.0
github.com/qdm12/govalid v0.1.0
github.com/qdm12/govalid v0.2.0-rc1
github.com/qdm12/log v0.1.0
github.com/qdm12/ss-server v0.4.0
github.com/qdm12/ss-server v0.5.0
github.com/qdm12/updated v0.0.0-20210603204757-205acfe6937e
github.com/stretchr/testify v1.8.2
github.com/stretchr/testify v1.8.4
github.com/ulikunitz/xz v0.5.11
github.com/vishvananda/netlink v1.2.1-beta.2
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a
golang.org/x/exp v0.0.0-20230519143937-03e91628a987
golang.org/x/net v0.10.0
golang.org/x/sys v0.8.0
golang.org/x/text v0.9.0
golang.org/x/net v0.19.0
golang.org/x/sys v0.15.0
golang.org/x/text v0.14.0
golang.zx2c4.com/wireguard v0.0.0-20230325221338-052af4a8072b
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230215201556-9c5414ab4bde
gopkg.in/ini.v1 v1.67.0
inet.af/netaddr v0.0.0-20220811202034-502d2d690317
)
@@ -32,7 +36,7 @@ require (
github.com/google/go-cmp v0.5.9 // indirect
github.com/josharian/native v1.0.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mdlayher/genetlink v1.2.0 // indirect
github.com/mdlayher/netlink v1.6.2 // indirect
github.com/mdlayher/socket v0.2.3 // indirect
@@ -42,8 +46,9 @@ 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-20211027215823-ae77deb06f29 // indirect
go4.org/unsafe/assume-no-moving-gc v0.0.0-20230221090011-e4bae7ad2296 // indirect
golang.org/x/crypto v0.6.0 // indirect
go4.org/unsafe/assume-no-moving-gc v0.0.0-20230525183740-e7c30c78aeb2 // indirect
golang.org/x/crypto v0.17.0 // indirect
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect

67
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.10 h1:UGVZ193UTSUASpGtg6pbDwzOd7XQP+at0Ssg1/2E4h8=
github.com/breml/rootcerts v0.2.10/go.mod h1:24FDtzYMpqIeYC7QzaE8VPRQaFZU5TIUDlyk8qwjD88=
github.com/breml/rootcerts v0.2.17 h1:0/M2BE2Apw0qEJCXDOkaiu7d5Sx5ObNfe1BkImJ4u1I=
github.com/breml/rootcerts v0.2.17/go.mod h1:S/PKh+4d1HUn4HQovEB8hPJZO6pUZYrIhmXBhsegfXw=
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=
@@ -14,8 +14,8 @@ github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDD
github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw=
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
@@ -37,6 +37,7 @@ 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=
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
@@ -49,6 +50,10 @@ github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJS
github.com/josharian/native v1.0.0 h1:Ts/E8zCSEsG17dUqv7joXJFybuMLjQfWE04tsBODTxk=
github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
@@ -62,8 +67,8 @@ github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxec
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mdlayher/genetlink v1.2.0 h1:4yrIkRV5Wfk1WfpWTcoOlGmsWgQj3OtQN9ZsbrE+XtU=
github.com/mdlayher/genetlink v1.2.0/go.mod h1:ra5LDov2KrUCZJiAtEvXXZBxGMInICMXIwshlJ+qRxQ=
github.com/mdlayher/netlink v1.6.0/go.mod h1:0o3PlBmGst1xve7wQ7j/hwpNaFaH4qCRyWCdcZk8/vA=
@@ -75,6 +80,7 @@ github.com/mdlayher/socket v0.2.3/go.mod h1:bz12/FozYNH/VbvC3q7TRIK/Y6dH1kCKsXaU
github.com/miekg/dns v1.1.40 h1:pyyPFfGMnciYUk/mXpKkVmeMQjfXqt3FAJ2hy7tPiLA=
github.com/miekg/dns v1.1.40/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721 h1:RlZweED6sbSArvlE924+mUcZuXKLBHA35U7LN621Bws=
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721/go.mod h1:Ickgr2WtCLZ2MDGd4Gr0geeCH5HybhRJbonOgQpvSxc=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
@@ -91,18 +97,20 @@ github.com/qdm12/golibs v0.0.0-20210603202746-e5494e9c2ebb/go.mod h1:15RBzkun0i8
github.com/qdm12/golibs v0.0.0-20210723175634-a75ca7fd74c2/go.mod h1:6aRbg4Z/bTbm9JfxsGXfWKHi7zsOvPfUTK1S5HuAFKg=
github.com/qdm12/golibs v0.0.0-20210822203818-5c568b0777b6 h1:bge5AL7cjHJMPz+5IOz5yF01q/l8No6+lIEBieA8gMg=
github.com/qdm12/golibs v0.0.0-20210822203818-5c568b0777b6/go.mod h1:6aRbg4Z/bTbm9JfxsGXfWKHi7zsOvPfUTK1S5HuAFKg=
github.com/qdm12/gosettings v0.4.0-rc1 h1:UYA92yyeDPbmZysIuG65yrpZVPtdIoRmtEHft/AyI38=
github.com/qdm12/gosettings v0.4.0-rc1/go.mod h1:JRV3opOpHvnKlIA29lKQMdYw1WSMVMfHYLLHPHol5ME=
github.com/qdm12/goshutdown v0.3.0 h1:pqBpJkdwlZlfTEx4QHtS8u8CXx6pG0fVo6S1N0MpSEM=
github.com/qdm12/goshutdown v0.3.0/go.mod h1:EqZ46No00kCTZ5qzdd3qIzY6ayhMt24QI8Mh8LVQYmM=
github.com/qdm12/gosplash v0.1.0 h1:Sfl+zIjFZFP7b0iqf2l5UkmEY97XBnaKkH3FNY6Gf7g=
github.com/qdm12/gosplash v0.1.0/go.mod h1:+A3fWW4/rUeDXhY3ieBzwghKdnIPFJgD8K3qQkenJlw=
github.com/qdm12/gotree v0.2.0 h1:+58ltxkNLUyHtATFereAcOjBVfY6ETqRex8XK90Fb/c=
github.com/qdm12/gotree v0.2.0/go.mod h1:1SdFaqKZuI46U1apbXIf25pDMNnrPuYLEqMF/qL4lY4=
github.com/qdm12/govalid v0.1.0 h1:UIFVmuaAg0Q+h0GeyfcFEZ5sQ5KJPvRQwycC1/cqDN8=
github.com/qdm12/govalid v0.1.0/go.mod h1:CyS/OEQdOvunBgrtIsW93fjd4jBkwZPBjGSpxq3NwA4=
github.com/qdm12/govalid v0.2.0-rc1 h1:4iYQvU4ibrASgzelsEgZX4JyKX3UTB/DcHObzQ7BXtw=
github.com/qdm12/govalid v0.2.0-rc1/go.mod h1:/uWzVWMuS71wmbsVnlUxpQiy6EAXqm8eQ2RbyA72roQ=
github.com/qdm12/log v0.1.0 h1:jYBd/xscHYpblzZAd2kjZp2YmuYHjAAfbTViJWxoPTw=
github.com/qdm12/log v0.1.0/go.mod h1:Vchi5M8uBvHfPNIblN4mjXn/oSbiWguQIbsgF1zdQPI=
github.com/qdm12/ss-server v0.4.0 h1:lMMYfDGc9P86Lyvd3+p8lK4hhgHUKDzjZC91FqJYkDU=
github.com/qdm12/ss-server v0.4.0/go.mod h1:AY0p4huvPUPW+/CiWsJcDgT6sneDryk26VXSccPNCxY=
github.com/qdm12/ss-server v0.5.0 h1:ARAqJayohDM51BmJ/R5Yplkpo+Qxgp7xizBF1HWd7uQ=
github.com/qdm12/ss-server v0.5.0/go.mod h1:eFd8PL/uy0ZvJ4KeSUzToruJctVQoYqXk+LRy9vcOiI=
github.com/qdm12/updated v0.0.0-20210603204757-205acfe6937e h1:4q+uFLawkaQRq3yARYLsjJPZd2wYwxn4g6G/5v0xW1g=
github.com/qdm12/updated v0.0.0-20210603204757-205acfe6937e/go.mod h1:UvJRGkZ9XL3/D7e7JiTTVLm1F3Cymd3/gFpD6frEpBo=
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
@@ -111,16 +119,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/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
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/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/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8=
github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/vishvananda/netlink v1.2.1-beta.2 h1:Llsql0lnQEbHj0I1OuKyp8otXp0r3q0mPkuhwHfStVs=
github.com/vishvananda/netlink v1.2.1-beta.2/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae h1:4hwBBUfQCFe3Cym0ZtKyq7L16eZUtYKs+BaHDN6mAns=
@@ -138,8 +144,8 @@ go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222175341-b30ae309168e/go.mod h1:
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-20211027215541-db492cf91b37/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20230221090011-e4bae7ad2296 h1:QJ/xcIANMLApehfgPCHnfK1hZiaMmbaTVmPv7DAoTbo=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20230221090011-e4bae7ad2296/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20230525183740-e7c30c78aeb2 h1:WJhcL4p+YeDxmZWg141nRm7XC8IDmhz7lk5GpadO1Sg=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20230525183740-e7c30c78aeb2/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=
@@ -148,10 +154,10 @@ golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/exp v0.0.0-20230519143937-03e91628a987 h1:3xJIFvzUFbu4ls0BTBYcgbCGhA63eAOEMxIHugyXJqA=
golang.org/x/exp v0.0.0-20230519143937-03e91628a987/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
@@ -167,8 +173,8 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b
golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220923203811-8be639271d50/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
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/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -197,8 +203,9 @@ golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -206,9 +213,10 @@ 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/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
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=
@@ -228,6 +236,8 @@ golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230215201556-9c5414ab4bde/go.mod h1:m
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98=
gopkg.in/src-d/go-git-fixtures.v3 v3.5.0/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g=
gopkg.in/src-d/go-git.v4 v4.13.1/go.mod h1:nx5NYcxdKxq5fpltdHnPa2Exj4Sx0EclMWZQbYDu2z8=
@@ -238,6 +248,7 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gvisor.dev/gvisor v0.0.0-20221203005347-703fd9b7fbc0 h1:Wobr37noukisGxpKo5jAsLREcpj61RxrWYzD8uwveOY=
gvisor.dev/gvisor v0.0.0-20221203005347-703fd9b7fbc0/go.mod h1:Dn5idtptoW1dIos9U6A2rpebLs/MtTwFacjKb8jLdQA=
inet.af/netaddr v0.0.0-20210511181906-37180328850c/go.mod h1:z0nx+Dh+7N7CC8V5ayHtHGpZpxLQZZxkIaaz6HN65Ls=
inet.af/netaddr v0.0.0-20220811202034-502d2d690317 h1:U2fwK6P2EqmopP/hFLTOAjWTki0qgd4GMJn5X8wOleU=
inet.af/netaddr v0.0.0-20220811202034-502d2d690317/go.mod h1:OIezDfdzOgFhuw4HuWapWq2e9l0H9tK4F1j+ETRtF3k=

View File

@@ -33,15 +33,20 @@ func addProviderFlag(flagSet *flag.FlagSet, providerToFormat map[string]*bool,
func (c *CLI) FormatServers(args []string) error {
var format, output string
allProviders := providers.All()
allProviderFlags := make([]string, len(allProviders))
for i, provider := range allProviders {
allProviderFlags[i] = strings.ReplaceAll(provider, " ", "-")
}
providersToFormat := make(map[string]*bool, len(allProviders))
for _, provider := range allProviders {
for _, provider := range allProviderFlags {
providersToFormat[provider] = new(bool)
}
flagSet := flag.NewFlagSet("markdown", flag.ExitOnError)
flagSet := flag.NewFlagSet("format-servers", 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")
titleCaser := cases.Title(language.English)
for _, provider := range allProviders {
for _, provider := range allProviderFlags {
addProviderFlag(flagSet, providersToFormat, provider, titleCaser)
}
if err := flagSet.Parse(args); err != nil {
@@ -68,7 +73,13 @@ func (c *CLI) FormatServers(args []string) error {
ErrMultipleProvidersToFormat, len(providers),
strings.Join(providers, ", "))
}
providerToFormat := providers[0]
var providerToFormat string
for _, providerToFormat = range allProviders {
if strings.ReplaceAll(providerToFormat, " ", "-") == providers[0] {
break
}
}
logger := newNoopLogger()
storage, err := storage.New(logger, constants.ServersData)

View File

@@ -9,9 +9,9 @@ import (
"time"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/openvpn/extract"
"github.com/qdm12/gluetun/internal/provider"
"github.com/qdm12/gluetun/internal/publicip/ipinfo"
"github.com/qdm12/gluetun/internal/storage"
"github.com/qdm12/gluetun/internal/updater/resolver"
)
@@ -32,7 +32,7 @@ type ParallelResolver interface {
}
type IPFetcher interface {
FetchMultiInfo(ctx context.Context, ips []netip.Addr) (data []ipinfo.Response, err error)
FetchInfo(ctx context.Context, ip netip.Addr) (data models.PublicIP, err error)
}
type IPv6Checker interface {

View File

@@ -14,7 +14,7 @@ import (
"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/publicip/api"
"github.com/qdm12/gluetun/internal/storage"
"github.com/qdm12/gluetun/internal/updater"
"github.com/qdm12/gluetun/internal/updater/resolver"
@@ -35,7 +35,7 @@ type UpdaterLogger interface {
func (c *CLI) Update(ctx context.Context, args []string, logger UpdaterLogger) error {
options := settings.Updater{}
var endUserMode, maintainerMode, updateAll bool
var csvProviders string
var csvProviders, ipToken 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,
@@ -46,6 +46,7 @@ func (c *CLI) Update(ctx context.Context, args []string, logger UpdaterLogger) e
"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")
flagSet.StringVar(&ipToken, "ip-token", "", "IP data service token (e.g. ipinfo.io) to use")
if err := flagSet.Parse(args); err != nil {
return err
}
@@ -79,7 +80,10 @@ func (c *CLI) Update(ctx context.Context, args []string, logger UpdaterLogger) e
httpClient := &http.Client{Timeout: clientTimeout}
unzipper := unzip.New(httpClient)
parallelResolver := resolver.NewParallelResolver(options.DNSAddress)
ipFetcher := ipinfo.New(httpClient)
ipFetcher, err := api.New(api.IPInfo, httpClient, ipToken)
if err != nil {
return fmt.Errorf("creating public IP API client: %w", err)
}
openvpnFileExtractor := extract.New()
providers := provider.NewProviders(storage, time.Now, logger, httpClient,

View File

@@ -4,7 +4,7 @@ import (
"fmt"
"net/netip"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gosettings"
"github.com/qdm12/gotree"
)
@@ -16,10 +16,14 @@ type DNS struct {
// DoT server. It cannot be the zero value in the internal
// state.
ServerAddress netip.Addr
// KeepNameserver is true if the Docker DNS server
// found in /etc/resolv.conf should be kept.
// Note settings this to true will go around the
// DoT server blocking.
// KeepNameserver is true if the existing DNS server
// found in /etc/resolv.conf should be used
// Note setting this to true will likely DNS traffic
// outside the VPN tunnel since it would go through
// the local DNS server of your Docker/Kubernetes
// configuration, which is likely not going through the tunnel.
// This will also disable the DNS over TLS server and the
// `ServerAddress` field will be ignored.
// It defaults to false and cannot be nil in the
// internal state.
KeepNameserver *bool
@@ -40,7 +44,7 @@ func (d DNS) validate() (err error) {
func (d *DNS) Copy() (copied DNS) {
return DNS{
ServerAddress: d.ServerAddress,
KeepNameserver: helpers.CopyPointer(d.KeepNameserver),
KeepNameserver: gosettings.CopyPointer(d.KeepNameserver),
DoT: d.DoT.copy(),
}
}
@@ -48,8 +52,8 @@ func (d *DNS) Copy() (copied DNS) {
// mergeWith merges the other settings into any
// unset field of the receiver settings object.
func (d *DNS) mergeWith(other DNS) {
d.ServerAddress = helpers.MergeWithIP(d.ServerAddress, other.ServerAddress)
d.KeepNameserver = helpers.MergeWithPointer(d.KeepNameserver, other.KeepNameserver)
d.ServerAddress = gosettings.MergeWithValidator(d.ServerAddress, other.ServerAddress)
d.KeepNameserver = gosettings.MergeWithPointer(d.KeepNameserver, other.KeepNameserver)
d.DoT.mergeWith(other.DoT)
}
@@ -57,15 +61,15 @@ func (d *DNS) mergeWith(other DNS) {
// settings object with any field set in the other
// settings.
func (d *DNS) overrideWith(other DNS) {
d.ServerAddress = helpers.OverrideWithIP(d.ServerAddress, other.ServerAddress)
d.KeepNameserver = helpers.OverrideWithPointer(d.KeepNameserver, other.KeepNameserver)
d.ServerAddress = gosettings.OverrideWithValidator(d.ServerAddress, other.ServerAddress)
d.KeepNameserver = gosettings.OverrideWithPointer(d.KeepNameserver, other.KeepNameserver)
d.DoT.overrideWith(other.DoT)
}
func (d *DNS) setDefaults() {
localhost := netip.AddrFrom4([4]byte{127, 0, 0, 1})
d.ServerAddress = helpers.DefaultIP(d.ServerAddress, localhost)
d.KeepNameserver = helpers.DefaultPointer(d.KeepNameserver, false)
d.ServerAddress = gosettings.DefaultValidator(d.ServerAddress, localhost)
d.KeepNameserver = gosettings.DefaultPointer(d.KeepNameserver, false)
d.DoT.setDefaults()
}
@@ -75,8 +79,11 @@ func (d DNS) String() string {
func (d DNS) toLinesNode() (node *gotree.Node) {
node = gotree.New("DNS settings:")
node.Appendf("Keep existing nameserver(s): %s", gosettings.BoolToYesNo(d.KeepNameserver))
if *d.KeepNameserver {
return node
}
node.Appendf("DNS server address to use: %s", d.ServerAddress)
node.Appendf("Keep existing nameserver(s): %s", helpers.BoolPtrToYesNo(d.KeepNameserver))
node.AppendNode(d.DoT.toLinesNode())
return node
}

View File

@@ -7,7 +7,7 @@ import (
"regexp"
"github.com/qdm12/dns/pkg/blacklist"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gosettings"
"github.com/qdm12/gotree"
)
@@ -23,9 +23,9 @@ type DNSBlacklist struct {
}
func (b *DNSBlacklist) setDefaults() {
b.BlockMalicious = helpers.DefaultPointer(b.BlockMalicious, true)
b.BlockAds = helpers.DefaultPointer(b.BlockAds, false)
b.BlockSurveillance = helpers.DefaultPointer(b.BlockSurveillance, true)
b.BlockMalicious = gosettings.DefaultPointer(b.BlockMalicious, true)
b.BlockAds = gosettings.DefaultPointer(b.BlockAds, false)
b.BlockSurveillance = gosettings.DefaultPointer(b.BlockSurveillance, true)
}
var hostRegex = regexp.MustCompile(`^([a-zA-Z0-9]|[a-zA-Z0-9_][a-zA-Z0-9\-_]{0,61}[a-zA-Z0-9_])(\.([a-zA-Z0-9]|[a-zA-Z0-9_][a-zA-Z0-9\-_]{0,61}[a-zA-Z0-9]))*$`) //nolint:lll
@@ -53,34 +53,34 @@ func (b DNSBlacklist) validate() (err error) {
func (b DNSBlacklist) copy() (copied DNSBlacklist) {
return DNSBlacklist{
BlockMalicious: helpers.CopyPointer(b.BlockMalicious),
BlockAds: helpers.CopyPointer(b.BlockAds),
BlockSurveillance: helpers.CopyPointer(b.BlockSurveillance),
AllowedHosts: helpers.CopySlice(b.AllowedHosts),
AddBlockedHosts: helpers.CopySlice(b.AddBlockedHosts),
AddBlockedIPs: helpers.CopySlice(b.AddBlockedIPs),
AddBlockedIPPrefixes: helpers.CopySlice(b.AddBlockedIPPrefixes),
BlockMalicious: gosettings.CopyPointer(b.BlockMalicious),
BlockAds: gosettings.CopyPointer(b.BlockAds),
BlockSurveillance: gosettings.CopyPointer(b.BlockSurveillance),
AllowedHosts: gosettings.CopySlice(b.AllowedHosts),
AddBlockedHosts: gosettings.CopySlice(b.AddBlockedHosts),
AddBlockedIPs: gosettings.CopySlice(b.AddBlockedIPs),
AddBlockedIPPrefixes: gosettings.CopySlice(b.AddBlockedIPPrefixes),
}
}
func (b *DNSBlacklist) mergeWith(other DNSBlacklist) {
b.BlockMalicious = helpers.MergeWithPointer(b.BlockMalicious, other.BlockMalicious)
b.BlockAds = helpers.MergeWithPointer(b.BlockAds, other.BlockAds)
b.BlockSurveillance = helpers.MergeWithPointer(b.BlockSurveillance, other.BlockSurveillance)
b.AllowedHosts = helpers.MergeSlices(b.AllowedHosts, other.AllowedHosts)
b.AddBlockedHosts = helpers.MergeSlices(b.AddBlockedHosts, other.AddBlockedHosts)
b.AddBlockedIPs = helpers.MergeSlices(b.AddBlockedIPs, other.AddBlockedIPs)
b.AddBlockedIPPrefixes = helpers.MergeSlices(b.AddBlockedIPPrefixes, other.AddBlockedIPPrefixes)
b.BlockMalicious = gosettings.MergeWithPointer(b.BlockMalicious, other.BlockMalicious)
b.BlockAds = gosettings.MergeWithPointer(b.BlockAds, other.BlockAds)
b.BlockSurveillance = gosettings.MergeWithPointer(b.BlockSurveillance, other.BlockSurveillance)
b.AllowedHosts = gosettings.MergeWithSlice(b.AllowedHosts, other.AllowedHosts)
b.AddBlockedHosts = gosettings.MergeWithSlice(b.AddBlockedHosts, other.AddBlockedHosts)
b.AddBlockedIPs = gosettings.MergeWithSlice(b.AddBlockedIPs, other.AddBlockedIPs)
b.AddBlockedIPPrefixes = gosettings.MergeWithSlice(b.AddBlockedIPPrefixes, other.AddBlockedIPPrefixes)
}
func (b *DNSBlacklist) overrideWith(other DNSBlacklist) {
b.BlockMalicious = helpers.OverrideWithPointer(b.BlockMalicious, other.BlockMalicious)
b.BlockAds = helpers.OverrideWithPointer(b.BlockAds, other.BlockAds)
b.BlockSurveillance = helpers.OverrideWithPointer(b.BlockSurveillance, other.BlockSurveillance)
b.AllowedHosts = helpers.OverrideWithSlice(b.AllowedHosts, other.AllowedHosts)
b.AddBlockedHosts = helpers.OverrideWithSlice(b.AddBlockedHosts, other.AddBlockedHosts)
b.AddBlockedIPs = helpers.OverrideWithSlice(b.AddBlockedIPs, other.AddBlockedIPs)
b.AddBlockedIPPrefixes = helpers.OverrideWithSlice(b.AddBlockedIPPrefixes, other.AddBlockedIPPrefixes)
b.BlockMalicious = gosettings.OverrideWithPointer(b.BlockMalicious, other.BlockMalicious)
b.BlockAds = gosettings.OverrideWithPointer(b.BlockAds, other.BlockAds)
b.BlockSurveillance = gosettings.OverrideWithPointer(b.BlockSurveillance, other.BlockSurveillance)
b.AllowedHosts = gosettings.OverrideWithSlice(b.AllowedHosts, other.AllowedHosts)
b.AddBlockedHosts = gosettings.OverrideWithSlice(b.AddBlockedHosts, other.AddBlockedHosts)
b.AddBlockedIPs = gosettings.OverrideWithSlice(b.AddBlockedIPs, other.AddBlockedIPs)
b.AddBlockedIPPrefixes = gosettings.OverrideWithSlice(b.AddBlockedIPPrefixes, other.AddBlockedIPPrefixes)
}
func (b DNSBlacklist) ToBlacklistFormat() (settings blacklist.BuilderSettings, err error) {
@@ -102,9 +102,9 @@ func (b DNSBlacklist) String() string {
func (b DNSBlacklist) toLinesNode() (node *gotree.Node) {
node = gotree.New("DNS filtering settings:")
node.Appendf("Block malicious: %s", helpers.BoolPtrToYesNo(b.BlockMalicious))
node.Appendf("Block ads: %s", helpers.BoolPtrToYesNo(b.BlockAds))
node.Appendf("Block surveillance: %s", helpers.BoolPtrToYesNo(b.BlockSurveillance))
node.Appendf("Block malicious: %s", gosettings.BoolToYesNo(b.BlockMalicious))
node.Appendf("Block ads: %s", gosettings.BoolToYesNo(b.BlockAds))
node.Appendf("Block surveillance: %s", gosettings.BoolToYesNo(b.BlockSurveillance))
if len(b.AllowedHosts) > 0 {
allowedHostsNode := node.Appendf("Allowed hosts:")

View File

@@ -5,7 +5,7 @@ import (
"fmt"
"time"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gosettings"
"github.com/qdm12/gotree"
)
@@ -54,8 +54,8 @@ func (d DoT) validate() (err error) {
func (d *DoT) copy() (copied DoT) {
return DoT{
Enabled: helpers.CopyPointer(d.Enabled),
UpdatePeriod: helpers.CopyPointer(d.UpdatePeriod),
Enabled: gosettings.CopyPointer(d.Enabled),
UpdatePeriod: gosettings.CopyPointer(d.UpdatePeriod),
Unbound: d.Unbound.copy(),
Blacklist: d.Blacklist.copy(),
}
@@ -64,8 +64,8 @@ func (d *DoT) copy() (copied DoT) {
// mergeWith merges the other settings into any
// unset field of the receiver settings object.
func (d *DoT) mergeWith(other DoT) {
d.Enabled = helpers.MergeWithPointer(d.Enabled, other.Enabled)
d.UpdatePeriod = helpers.MergeWithPointer(d.UpdatePeriod, other.UpdatePeriod)
d.Enabled = gosettings.MergeWithPointer(d.Enabled, other.Enabled)
d.UpdatePeriod = gosettings.MergeWithPointer(d.UpdatePeriod, other.UpdatePeriod)
d.Unbound.mergeWith(other.Unbound)
d.Blacklist.mergeWith(other.Blacklist)
}
@@ -74,16 +74,16 @@ func (d *DoT) mergeWith(other DoT) {
// settings object with any field set in the other
// settings.
func (d *DoT) overrideWith(other DoT) {
d.Enabled = helpers.OverrideWithPointer(d.Enabled, other.Enabled)
d.UpdatePeriod = helpers.OverrideWithPointer(d.UpdatePeriod, other.UpdatePeriod)
d.Enabled = gosettings.OverrideWithPointer(d.Enabled, other.Enabled)
d.UpdatePeriod = gosettings.OverrideWithPointer(d.UpdatePeriod, other.UpdatePeriod)
d.Unbound.overrideWith(other.Unbound)
d.Blacklist.overrideWith(other.Blacklist)
}
func (d *DoT) setDefaults() {
d.Enabled = helpers.DefaultPointer(d.Enabled, true)
d.Enabled = gosettings.DefaultPointer(d.Enabled, true)
const defaultUpdatePeriod = 24 * time.Hour
d.UpdatePeriod = helpers.DefaultPointer(d.UpdatePeriod, defaultUpdatePeriod)
d.UpdatePeriod = gosettings.DefaultPointer(d.UpdatePeriod, defaultUpdatePeriod)
d.Unbound.setDefaults()
d.Blacklist.setDefaults()
}
@@ -95,12 +95,12 @@ func (d DoT) String() string {
func (d DoT) toLinesNode() (node *gotree.Node) {
node = gotree.New("DNS over TLS settings:")
node.Appendf("Enabled: %s", helpers.BoolPtrToYesNo(d.Enabled))
node.Appendf("Enabled: %s", gosettings.BoolToYesNo(d.Enabled))
if !*d.Enabled {
return node
}
update := "disabled"
update := "disabled" //nolint:goconst
if *d.UpdatePeriod > 0 {
update = "every " + d.UpdatePeriod.String()
}

View File

@@ -5,9 +5,11 @@ import "errors"
var (
ErrCityNotValid = errors.New("the city specified is not valid")
ErrControlServerPrivilegedPort = errors.New("cannot use privileged port without running as root")
ErrCategoryNotValid = errors.New("the category specified is not valid")
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")
ErrFirewallZeroPort = errors.New("cannot have a zero port")
ErrFirewallPublicOutboundSubnet = errors.New("outbound subnet has an unspecified address")
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")
@@ -34,6 +36,8 @@ var (
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")
ErrWireguardAllowedIPNotSet = errors.New("allowed IP is not set")
ErrWireguardAllowedIPsNotSet = errors.New("allowed IPs is not set")
ErrWireguardEndpointIPNotSet = errors.New("endpoint IP is not set")
ErrWireguardEndpointPortNotAllowed = errors.New("endpoint port is not allowed")
ErrWireguardEndpointPortNotSet = errors.New("endpoint port is not set")

View File

@@ -4,7 +4,7 @@ import (
"fmt"
"net/netip"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gosettings"
"github.com/qdm12/gotree"
)
@@ -26,6 +26,12 @@ func (f Firewall) validate() (err error) {
return fmt.Errorf("input ports: %w", ErrFirewallZeroPort)
}
for _, subnet := range f.OutboundSubnets {
if subnet.Addr().IsUnspecified() {
return fmt.Errorf("%w: %s", ErrFirewallPublicOutboundSubnet, subnet)
}
}
return nil
}
@@ -40,11 +46,11 @@ func hasZeroPort(ports []uint16) (has bool) {
func (f *Firewall) copy() (copied Firewall) {
return Firewall{
VPNInputPorts: helpers.CopySlice(f.VPNInputPorts),
InputPorts: helpers.CopySlice(f.InputPorts),
OutboundSubnets: helpers.CopySlice(f.OutboundSubnets),
Enabled: helpers.CopyPointer(f.Enabled),
Debug: helpers.CopyPointer(f.Debug),
VPNInputPorts: gosettings.CopySlice(f.VPNInputPorts),
InputPorts: gosettings.CopySlice(f.InputPorts),
OutboundSubnets: gosettings.CopySlice(f.OutboundSubnets),
Enabled: gosettings.CopyPointer(f.Enabled),
Debug: gosettings.CopyPointer(f.Debug),
}
}
@@ -53,27 +59,27 @@ func (f *Firewall) copy() (copied Firewall) {
// It merges values of slices together, even if they
// are set in the receiver settings.
func (f *Firewall) mergeWith(other Firewall) {
f.VPNInputPorts = helpers.MergeSlices(f.VPNInputPorts, other.VPNInputPorts)
f.InputPorts = helpers.MergeSlices(f.InputPorts, other.InputPorts)
f.OutboundSubnets = helpers.MergeSlices(f.OutboundSubnets, other.OutboundSubnets)
f.Enabled = helpers.MergeWithPointer(f.Enabled, other.Enabled)
f.Debug = helpers.MergeWithPointer(f.Debug, other.Debug)
f.VPNInputPorts = gosettings.MergeWithSlice(f.VPNInputPorts, other.VPNInputPorts)
f.InputPorts = gosettings.MergeWithSlice(f.InputPorts, other.InputPorts)
f.OutboundSubnets = gosettings.MergeWithSlice(f.OutboundSubnets, other.OutboundSubnets)
f.Enabled = gosettings.MergeWithPointer(f.Enabled, other.Enabled)
f.Debug = gosettings.MergeWithPointer(f.Debug, other.Debug)
}
// overrideWith overrides fields of the receiver
// settings object with any field set in the other
// settings.
func (f *Firewall) overrideWith(other Firewall) {
f.VPNInputPorts = helpers.OverrideWithSlice(f.VPNInputPorts, other.VPNInputPorts)
f.InputPorts = helpers.OverrideWithSlice(f.InputPorts, other.InputPorts)
f.OutboundSubnets = helpers.OverrideWithSlice(f.OutboundSubnets, other.OutboundSubnets)
f.Enabled = helpers.OverrideWithPointer(f.Enabled, other.Enabled)
f.Debug = helpers.OverrideWithPointer(f.Debug, other.Debug)
f.VPNInputPorts = gosettings.OverrideWithSlice(f.VPNInputPorts, other.VPNInputPorts)
f.InputPorts = gosettings.OverrideWithSlice(f.InputPorts, other.InputPorts)
f.OutboundSubnets = gosettings.OverrideWithSlice(f.OutboundSubnets, other.OutboundSubnets)
f.Enabled = gosettings.OverrideWithPointer(f.Enabled, other.Enabled)
f.Debug = gosettings.OverrideWithPointer(f.Debug, other.Debug)
}
func (f *Firewall) setDefaults() {
f.Enabled = helpers.DefaultPointer(f.Enabled, true)
f.Debug = helpers.DefaultPointer(f.Debug, false)
f.Enabled = gosettings.DefaultPointer(f.Enabled, true)
f.Debug = gosettings.DefaultPointer(f.Debug, false)
}
func (f Firewall) String() string {
@@ -83,7 +89,7 @@ func (f Firewall) String() string {
func (f Firewall) toLinesNode() (node *gotree.Node) {
node = gotree.New("Firewall settings:")
node.Appendf("Enabled: %s", helpers.BoolPtrToYesNo(f.Enabled))
node.Appendf("Enabled: %s", gosettings.BoolToYesNo(f.Enabled))
if !*f.Enabled {
return node
}

View File

@@ -0,0 +1,74 @@
package settings
import (
"net/netip"
"testing"
"github.com/stretchr/testify/assert"
)
func Test_Firewall_validate(t *testing.T) {
t.Parallel()
testCases := map[string]struct {
firewall Firewall
errWrapped error
errMessage string
}{
"empty": {},
"zero_vpn_input_port": {
firewall: Firewall{
VPNInputPorts: []uint16{0},
},
errWrapped: ErrFirewallZeroPort,
errMessage: "VPN input ports: cannot have a zero port",
},
"zero_input_port": {
firewall: Firewall{
InputPorts: []uint16{0},
},
errWrapped: ErrFirewallZeroPort,
errMessage: "input ports: cannot have a zero port",
},
"unspecified_outbound_subnet": {
firewall: Firewall{
OutboundSubnets: []netip.Prefix{
netip.MustParsePrefix("0.0.0.0/0"),
},
},
errWrapped: ErrFirewallPublicOutboundSubnet,
errMessage: "outbound subnet has an unspecified address: 0.0.0.0/0",
},
"public_outbound_subnet": {
firewall: Firewall{
OutboundSubnets: []netip.Prefix{
netip.MustParsePrefix("1.2.3.4/32"),
},
},
},
"valid_settings": {
firewall: Firewall{
VPNInputPorts: []uint16{100, 101},
InputPorts: []uint16{200, 201},
OutboundSubnets: []netip.Prefix{
netip.MustParsePrefix("192.168.1.0/24"),
netip.MustParsePrefix("10.10.1.1/32"),
},
},
},
}
for name, testCase := range testCases {
testCase := testCase
t.Run(name, func(t *testing.T) {
t.Parallel()
err := testCase.firewall.validate()
assert.ErrorIs(t, err, testCase.errWrapped)
if testCase.errWrapped != nil {
assert.EqualError(t, err, testCase.errMessage)
}
})
}
}

View File

@@ -5,7 +5,7 @@ import (
"os"
"time"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gosettings"
"github.com/qdm12/gotree"
"github.com/qdm12/govalid/address"
)
@@ -37,7 +37,7 @@ type Health struct {
func (h Health) Validate() (err error) {
uid := os.Getuid()
_, err = address.Validate(h.ServerAddress,
err = address.Validate(h.ServerAddress,
address.OptionListening(uid))
if err != nil {
return fmt.Errorf("server listening address is not valid: %w", err)
@@ -65,11 +65,11 @@ func (h *Health) copy() (copied Health) {
// MergeWith merges the other settings into any
// unset field of the receiver settings object.
func (h *Health) MergeWith(other Health) {
h.ServerAddress = helpers.MergeWithString(h.ServerAddress, other.ServerAddress)
h.ReadHeaderTimeout = helpers.MergeWithNumber(h.ReadHeaderTimeout, other.ReadHeaderTimeout)
h.ReadTimeout = helpers.MergeWithNumber(h.ReadTimeout, other.ReadTimeout)
h.TargetAddress = helpers.MergeWithString(h.TargetAddress, other.TargetAddress)
h.SuccessWait = helpers.MergeWithNumber(h.SuccessWait, other.SuccessWait)
h.ServerAddress = gosettings.MergeWithString(h.ServerAddress, other.ServerAddress)
h.ReadHeaderTimeout = gosettings.MergeWithNumber(h.ReadHeaderTimeout, other.ReadHeaderTimeout)
h.ReadTimeout = gosettings.MergeWithNumber(h.ReadTimeout, other.ReadTimeout)
h.TargetAddress = gosettings.MergeWithString(h.TargetAddress, other.TargetAddress)
h.SuccessWait = gosettings.MergeWithNumber(h.SuccessWait, other.SuccessWait)
h.VPN.mergeWith(other.VPN)
}
@@ -77,23 +77,23 @@ func (h *Health) MergeWith(other Health) {
// settings object with any field set in the other
// settings.
func (h *Health) OverrideWith(other Health) {
h.ServerAddress = helpers.OverrideWithString(h.ServerAddress, other.ServerAddress)
h.ReadHeaderTimeout = helpers.OverrideWithNumber(h.ReadHeaderTimeout, other.ReadHeaderTimeout)
h.ReadTimeout = helpers.OverrideWithNumber(h.ReadTimeout, other.ReadTimeout)
h.TargetAddress = helpers.OverrideWithString(h.TargetAddress, other.TargetAddress)
h.SuccessWait = helpers.OverrideWithNumber(h.SuccessWait, other.SuccessWait)
h.ServerAddress = gosettings.OverrideWithString(h.ServerAddress, other.ServerAddress)
h.ReadHeaderTimeout = gosettings.OverrideWithNumber(h.ReadHeaderTimeout, other.ReadHeaderTimeout)
h.ReadTimeout = gosettings.OverrideWithNumber(h.ReadTimeout, other.ReadTimeout)
h.TargetAddress = gosettings.OverrideWithString(h.TargetAddress, other.TargetAddress)
h.SuccessWait = gosettings.OverrideWithNumber(h.SuccessWait, other.SuccessWait)
h.VPN.overrideWith(other.VPN)
}
func (h *Health) SetDefaults() {
h.ServerAddress = helpers.DefaultString(h.ServerAddress, "127.0.0.1:9999")
h.ServerAddress = gosettings.DefaultString(h.ServerAddress, "127.0.0.1:9999")
const defaultReadHeaderTimeout = 100 * time.Millisecond
h.ReadHeaderTimeout = helpers.DefaultNumber(h.ReadHeaderTimeout, defaultReadHeaderTimeout)
h.ReadHeaderTimeout = gosettings.DefaultNumber(h.ReadHeaderTimeout, defaultReadHeaderTimeout)
const defaultReadTimeout = 500 * time.Millisecond
h.ReadTimeout = helpers.DefaultNumber(h.ReadTimeout, defaultReadTimeout)
h.TargetAddress = helpers.DefaultString(h.TargetAddress, "cloudflare.com:443")
h.ReadTimeout = gosettings.DefaultNumber(h.ReadTimeout, defaultReadTimeout)
h.TargetAddress = gosettings.DefaultString(h.TargetAddress, "cloudflare.com:443")
const defaultSuccessWait = 5 * time.Second
h.SuccessWait = helpers.DefaultNumber(h.SuccessWait, defaultSuccessWait)
h.SuccessWait = gosettings.DefaultNumber(h.SuccessWait, defaultSuccessWait)
h.VPN.setDefaults()
}

View File

@@ -3,7 +3,7 @@ package settings
import (
"time"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gosettings"
"github.com/qdm12/gotree"
)
@@ -27,31 +27,31 @@ func (h HealthyWait) validate() (err error) {
// unset field of the receiver settings object.
func (h *HealthyWait) copy() (copied HealthyWait) {
return HealthyWait{
Initial: helpers.CopyPointer(h.Initial),
Addition: helpers.CopyPointer(h.Addition),
Initial: gosettings.CopyPointer(h.Initial),
Addition: gosettings.CopyPointer(h.Addition),
}
}
// mergeWith merges the other settings into any
// unset field of the receiver settings object.
func (h *HealthyWait) mergeWith(other HealthyWait) {
h.Initial = helpers.MergeWithPointer(h.Initial, other.Initial)
h.Addition = helpers.MergeWithPointer(h.Addition, other.Addition)
h.Initial = gosettings.MergeWithPointer(h.Initial, other.Initial)
h.Addition = gosettings.MergeWithPointer(h.Addition, other.Addition)
}
// overrideWith overrides fields of the receiver
// settings object with any field set in the other
// settings.
func (h *HealthyWait) overrideWith(other HealthyWait) {
h.Initial = helpers.OverrideWithPointer(h.Initial, other.Initial)
h.Addition = helpers.OverrideWithPointer(h.Addition, other.Addition)
h.Initial = gosettings.OverrideWithPointer(h.Initial, other.Initial)
h.Addition = gosettings.OverrideWithPointer(h.Addition, other.Addition)
}
func (h *HealthyWait) setDefaults() {
const initialDurationDefault = 6 * time.Second
const additionDurationDefault = 5 * time.Second
h.Initial = helpers.DefaultPointer(h.Initial, initialDurationDefault)
h.Addition = helpers.DefaultPointer(h.Addition, additionDurationDefault)
h.Initial = gosettings.DefaultPointer(h.Initial, initialDurationDefault)
h.Addition = gosettings.DefaultPointer(h.Addition, additionDurationDefault)
}
func (h HealthyWait) String() string {

View File

@@ -1,12 +1,6 @@
package helpers
import (
"errors"
"fmt"
"strings"
)
func IsOneOf(value string, choices ...string) (ok bool) {
func IsOneOf[T comparable](value T, choices ...T) (ok bool) {
for _, choice := range choices {
if value == choice {
return true
@@ -14,39 +8,3 @@ func IsOneOf(value string, choices ...string) (ok bool) {
}
return false
}
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 fmt.Errorf("%w", ErrNoChoice)
}
set := make(map[string]struct{}, len(choices))
for _, choice := range choices {
choice = strings.ToLower(choice)
set[choice] = struct{}{}
}
for _, value := range values {
_, ok := set[value]
if !ok {
return fmt.Errorf("%w: value %q, choices available are %s",
ErrValueNotOneOf, value, strings.Join(choices, ", "))
}
}
return nil
}
func Uint16IsOneOf(port uint16, choices []uint16) (ok bool) {
for _, choice := range choices {
if port == choice {
return true
}
}
return false
}

View File

@@ -1,20 +0,0 @@
package helpers
import (
"net/netip"
"golang.org/x/exp/slices"
)
func CopyPointer[T any](original *T) (copied *T) {
if original == nil {
return nil
}
copied = new(T)
*copied = *original
return copied
}
func CopySlice[T string | uint16 | netip.Addr | netip.Prefix](original []T) (copied []T) {
return slices.Clone(original)
}

View File

@@ -1,39 +0,0 @@
package helpers
import (
"net/netip"
)
func DefaultPointer[T any](existing *T, defaultValue T) (
result *T) {
if existing != nil {
return existing
}
result = new(T)
*result = defaultValue
return result
}
func DefaultString(existing string, defaultValue string) (
result string) {
if existing != "" {
return existing
}
return defaultValue
}
func DefaultNumber[T Number](existing T, defaultValue T) ( //nolint:ireturn
result T) {
if existing != 0 {
return existing
}
return defaultValue
}
func DefaultIP(existing netip.Addr, defaultValue netip.Addr) (
result netip.Addr) {
if existing.IsValid() {
return existing
}
return defaultValue
}

View File

@@ -1,31 +0,0 @@
package helpers
import (
"errors"
"fmt"
"os"
"path/filepath"
)
var (
ErrFileDoesNotExist = errors.New("file does not exist")
ErrFileRead = errors.New("cannot read file")
ErrFileClose = errors.New("cannot close file")
)
func FileExists(path string) (err error) {
path = filepath.Clean(path)
f, err := os.Open(path)
if errors.Is(err, os.ErrNotExist) {
return fmt.Errorf("%w: %s", ErrFileDoesNotExist, path)
} else if err != nil {
return fmt.Errorf("%w: %s", ErrFileRead, err)
}
if err := f.Close(); err != nil {
return fmt.Errorf("%w: %s", ErrFileClose, err)
}
return nil
}

View File

@@ -1,10 +0,0 @@
package helpers
import "time"
type Number interface {
uint8 | uint16 | uint32 | uint64 | uint |
int8 | int16 | int32 | int64 | int |
float32 | float64 |
time.Duration
}

View File

@@ -1,69 +0,0 @@
package helpers
import (
"net/http"
"net/netip"
)
func MergeWithPointer[T any](existing, other *T) (result *T) {
if existing != nil {
return existing
} else if other == nil {
return nil
}
result = new(T)
*result = *other
return result
}
func MergeWithString(existing, other string) (result string) {
if existing != "" {
return existing
}
return other
}
func MergeWithNumber[T Number](existing, other T) (result T) { //nolint:ireturn
if existing != 0 {
return existing
}
return other
}
func MergeWithIP(existing, other netip.Addr) (result netip.Addr) {
if existing.IsValid() {
return existing
}
return other
}
func MergeWithHTTPHandler(existing, other http.Handler) (result http.Handler) {
if existing != nil {
return existing
}
return other
}
func MergeSlices[T comparable](a, b []T) (result []T) {
if a == nil && b == nil {
return nil
}
seen := make(map[T]struct{}, len(a)+len(b))
result = make([]T, 0, len(a)+len(b))
for _, s := range a {
if _, ok := seen[s]; ok {
continue // duplicate
}
result = append(result, s)
seen[s] = struct{}{}
}
for _, s := range b {
if _, ok := seen[s]; ok {
continue // duplicate
}
result = append(result, s)
seen[s] = struct{}{}
}
return result
}

View File

@@ -1,29 +0,0 @@
package helpers
import (
"fmt"
"strings"
)
func ChoicesOrString(choices []string) string {
return strings.Join(
choices[:len(choices)-1], ", ") +
" or " + choices[len(choices)-1]
}
func PortChoicesOrString(ports []uint16) (s string) {
switch len(ports) {
case 0:
return "there is no allowed port"
case 1:
return "allowed port is " + fmt.Sprint(ports[0])
}
s = "allowed ports are "
portStrings := make([]string, len(ports))
for i := range ports {
portStrings[i] = fmt.Sprint(ports[i])
}
s += ChoicesOrString(portStrings)
return s
}

View File

@@ -1,25 +0,0 @@
package helpers
func ObfuscateWireguardKey(fullKey string) (obfuscatedKey string) {
const minKeyLength = 10
if len(fullKey) < minKeyLength {
return "(too short)"
}
lastIndex := len(fullKey) - 1
return fullKey[0:2] + "..." + fullKey[lastIndex-2:]
}
func ObfuscatePassword(password string) (obfuscatedPassword string) {
if password != "" {
return "[set]"
}
return "[not set]"
}
func ObfuscateData(data string) (obfuscated string) {
if data != "" {
return "[set]"
}
return "[not set]"
}

View File

@@ -1,52 +0,0 @@
package helpers
import (
"net/http"
"net/netip"
)
func OverrideWithPointer[T any](existing, other *T) (result *T) {
if other == nil {
return existing
}
result = new(T)
*result = *other
return result
}
func OverrideWithString(existing, other string) (result string) {
if other == "" {
return existing
}
return other
}
func OverrideWithNumber[T Number](existing, other T) (result T) { //nolint:ireturn
if other == 0 {
return existing
}
return other
}
func OverrideWithIP(existing, other netip.Addr) (result netip.Addr) {
if !other.IsValid() {
return existing
}
return other
}
func OverrideWithHTTPHandler(existing, other http.Handler) (result http.Handler) {
if other != nil {
return other
}
return existing
}
func OverrideWithSlice[T any](existing, other []T) (result []T) {
if other == nil {
return existing
}
result = make([]T, len(other))
copy(result, other)
return result
}

View File

@@ -1,12 +1,5 @@
package helpers
func BoolPtrToYesNo(b *bool) string {
if *b {
return "yes"
}
return "no"
}
func TCPPtrToString(tcp *bool) string {
if *tcp {
return "TCP"

View File

@@ -5,7 +5,7 @@ import (
"os"
"time"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gosettings"
"github.com/qdm12/gotree"
"github.com/qdm12/govalid/address"
)
@@ -46,7 +46,7 @@ func (h HTTPProxy) validate() (err error) {
// Do not validate user and password
uid := os.Getuid()
_, err = address.Validate(h.ListeningAddress, address.OptionListening(uid))
err = address.Validate(h.ListeningAddress, address.OptionListening(uid))
if err != nil {
return fmt.Errorf("%w: %s", ErrServerAddressNotValid, h.ListeningAddress)
}
@@ -56,12 +56,12 @@ func (h HTTPProxy) validate() (err error) {
func (h *HTTPProxy) copy() (copied HTTPProxy) {
return HTTPProxy{
User: helpers.CopyPointer(h.User),
Password: helpers.CopyPointer(h.Password),
User: gosettings.CopyPointer(h.User),
Password: gosettings.CopyPointer(h.Password),
ListeningAddress: h.ListeningAddress,
Enabled: helpers.CopyPointer(h.Enabled),
Stealth: helpers.CopyPointer(h.Stealth),
Log: helpers.CopyPointer(h.Log),
Enabled: gosettings.CopyPointer(h.Enabled),
Stealth: gosettings.CopyPointer(h.Stealth),
Log: gosettings.CopyPointer(h.Log),
ReadHeaderTimeout: h.ReadHeaderTimeout,
ReadTimeout: h.ReadTimeout,
}
@@ -70,41 +70,41 @@ func (h *HTTPProxy) copy() (copied HTTPProxy) {
// mergeWith merges the other settings into any
// unset field of the receiver settings object.
func (h *HTTPProxy) mergeWith(other HTTPProxy) {
h.User = helpers.MergeWithPointer(h.User, other.User)
h.Password = helpers.MergeWithPointer(h.Password, other.Password)
h.ListeningAddress = helpers.MergeWithString(h.ListeningAddress, other.ListeningAddress)
h.Enabled = helpers.MergeWithPointer(h.Enabled, other.Enabled)
h.Stealth = helpers.MergeWithPointer(h.Stealth, other.Stealth)
h.Log = helpers.MergeWithPointer(h.Log, other.Log)
h.ReadHeaderTimeout = helpers.MergeWithNumber(h.ReadHeaderTimeout, other.ReadHeaderTimeout)
h.ReadTimeout = helpers.MergeWithNumber(h.ReadTimeout, other.ReadTimeout)
h.User = gosettings.MergeWithPointer(h.User, other.User)
h.Password = gosettings.MergeWithPointer(h.Password, other.Password)
h.ListeningAddress = gosettings.MergeWithString(h.ListeningAddress, other.ListeningAddress)
h.Enabled = gosettings.MergeWithPointer(h.Enabled, other.Enabled)
h.Stealth = gosettings.MergeWithPointer(h.Stealth, other.Stealth)
h.Log = gosettings.MergeWithPointer(h.Log, other.Log)
h.ReadHeaderTimeout = gosettings.MergeWithNumber(h.ReadHeaderTimeout, other.ReadHeaderTimeout)
h.ReadTimeout = gosettings.MergeWithNumber(h.ReadTimeout, other.ReadTimeout)
}
// overrideWith overrides fields of the receiver
// settings object with any field set in the other
// settings.
func (h *HTTPProxy) overrideWith(other HTTPProxy) {
h.User = helpers.OverrideWithPointer(h.User, other.User)
h.Password = helpers.OverrideWithPointer(h.Password, other.Password)
h.ListeningAddress = helpers.OverrideWithString(h.ListeningAddress, other.ListeningAddress)
h.Enabled = helpers.OverrideWithPointer(h.Enabled, other.Enabled)
h.Stealth = helpers.OverrideWithPointer(h.Stealth, other.Stealth)
h.Log = helpers.OverrideWithPointer(h.Log, other.Log)
h.ReadHeaderTimeout = helpers.OverrideWithNumber(h.ReadHeaderTimeout, other.ReadHeaderTimeout)
h.ReadTimeout = helpers.OverrideWithNumber(h.ReadTimeout, other.ReadTimeout)
h.User = gosettings.OverrideWithPointer(h.User, other.User)
h.Password = gosettings.OverrideWithPointer(h.Password, other.Password)
h.ListeningAddress = gosettings.OverrideWithString(h.ListeningAddress, other.ListeningAddress)
h.Enabled = gosettings.OverrideWithPointer(h.Enabled, other.Enabled)
h.Stealth = gosettings.OverrideWithPointer(h.Stealth, other.Stealth)
h.Log = gosettings.OverrideWithPointer(h.Log, other.Log)
h.ReadHeaderTimeout = gosettings.OverrideWithNumber(h.ReadHeaderTimeout, other.ReadHeaderTimeout)
h.ReadTimeout = gosettings.OverrideWithNumber(h.ReadTimeout, other.ReadTimeout)
}
func (h *HTTPProxy) setDefaults() {
h.User = helpers.DefaultPointer(h.User, "")
h.Password = helpers.DefaultPointer(h.Password, "")
h.ListeningAddress = helpers.DefaultString(h.ListeningAddress, ":8888")
h.Enabled = helpers.DefaultPointer(h.Enabled, false)
h.Stealth = helpers.DefaultPointer(h.Stealth, false)
h.Log = helpers.DefaultPointer(h.Log, false)
h.User = gosettings.DefaultPointer(h.User, "")
h.Password = gosettings.DefaultPointer(h.Password, "")
h.ListeningAddress = gosettings.DefaultString(h.ListeningAddress, ":8888")
h.Enabled = gosettings.DefaultPointer(h.Enabled, false)
h.Stealth = gosettings.DefaultPointer(h.Stealth, false)
h.Log = gosettings.DefaultPointer(h.Log, false)
const defaultReadHeaderTimeout = time.Second
h.ReadHeaderTimeout = helpers.DefaultNumber(h.ReadHeaderTimeout, defaultReadHeaderTimeout)
h.ReadHeaderTimeout = gosettings.DefaultNumber(h.ReadHeaderTimeout, defaultReadHeaderTimeout)
const defaultReadTimeout = 3 * time.Second
h.ReadTimeout = helpers.DefaultNumber(h.ReadTimeout, defaultReadTimeout)
h.ReadTimeout = gosettings.DefaultNumber(h.ReadTimeout, defaultReadTimeout)
}
func (h HTTPProxy) String() string {
@@ -113,16 +113,16 @@ func (h HTTPProxy) String() string {
func (h HTTPProxy) toLinesNode() (node *gotree.Node) {
node = gotree.New("HTTP proxy settings:")
node.Appendf("Enabled: %s", helpers.BoolPtrToYesNo(h.Enabled))
node.Appendf("Enabled: %s", gosettings.BoolToYesNo(h.Enabled))
if !*h.Enabled {
return node
}
node.Appendf("Listening address: %s", h.ListeningAddress)
node.Appendf("User: %s", *h.User)
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("Password: %s", gosettings.ObfuscateKey(*h.Password))
node.Appendf("Stealth mode: %s", gosettings.BoolToYesNo(h.Stealth))
node.Appendf("Log: %s", gosettings.BoolToYesNo(h.Log))
node.Appendf("Read header timeout: %s", h.ReadHeaderTimeout)
node.Appendf("Read timeout: %s", h.ReadTimeout)

View File

@@ -1,7 +1,7 @@
package settings
import (
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gosettings"
"github.com/qdm12/gotree"
"github.com/qdm12/log"
)
@@ -19,25 +19,25 @@ func (l Log) validate() (err error) {
func (l *Log) copy() (copied Log) {
return Log{
Level: helpers.CopyPointer(l.Level),
Level: gosettings.CopyPointer(l.Level),
}
}
// mergeWith merges the other settings into any
// unset field of the receiver settings object.
func (l *Log) mergeWith(other Log) {
l.Level = helpers.MergeWithPointer(l.Level, other.Level)
l.Level = gosettings.MergeWithPointer(l.Level, other.Level)
}
// overrideWith overrides fields of the receiver
// settings object with any field set in the other
// settings.
func (l *Log) overrideWith(other Log) {
l.Level = helpers.OverrideWithPointer(l.Level, other.Level)
l.Level = gosettings.OverrideWithPointer(l.Level, other.Level)
}
func (l *Log) setDefaults() {
l.Level = helpers.DefaultPointer(l.Level, log.LevelInfo)
l.Level = gosettings.DefaultPointer(l.Level, log.LevelInfo)
}
func (l Log) String() string {

View File

@@ -0,0 +1,42 @@
package settings
// Retro-compatibility because SERVER_REGIONS changed to SERVER_COUNTRIES
// and SERVER_REGIONS is now the continent field for servers.
// TODO v4 remove.
func nordvpnRetroRegion(selection ServerSelection, validRegions, validCountries []string) (
updatedSelection ServerSelection) {
validRegionsMap := stringSliceToMap(validRegions)
validCountriesMap := stringSliceToMap(validCountries)
updatedSelection = selection.copy()
updatedSelection.Regions = make([]string, 0, len(selection.Regions))
for _, region := range selection.Regions {
_, isValid := validRegionsMap[region]
if isValid {
updatedSelection.Regions = append(updatedSelection.Regions, region)
continue
}
_, isValid = validCountriesMap[region]
if !isValid {
// Region is not valid for the country or region
// just leave it to the validation to fail it later
continue
}
// Region is not valid for a region, but is a valid country
// Handle retro-compatibility and transfer the value to the
// country field.
updatedSelection.Countries = append(updatedSelection.Countries, region)
}
return updatedSelection
}
func stringSliceToMap(slice []string) (m map[string]struct{}) {
m = make(map[string]struct{}, len(slice))
for _, s := range slice {
m[s] = struct{}{}
}
return m
}

View File

@@ -4,94 +4,93 @@ import (
"encoding/base64"
"fmt"
"regexp"
"strings"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/constants/openvpn"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/openvpn/extract"
"github.com/qdm12/gluetun/internal/provider/privateinternetaccess/presets"
"github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/validate"
"github.com/qdm12/gotree"
)
// OpenVPN contains settings to configure the OpenVPN client.
type OpenVPN struct {
// Version is the OpenVPN version to run.
// It can only be "2.4" or "2.5".
Version string
// It can only be "2.5" or "2.6".
Version string `json:"version"`
// User is the OpenVPN authentication username.
// 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
User *string `json:"user"`
// Password is the OpenVPN authentication password.
// 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
Password *string `json:"password"`
// 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.
ConfFile *string
ConfFile *string `json:"config_file_path"`
// Ciphers is a list of ciphers to use for OpenVPN,
// different from the ones specified by the VPN
// service provider configuration files.
Ciphers []string
Ciphers []string `json:"ciphers"`
// Auth is an auth algorithm to use in OpenVPN instead
// of the one specified by the VPN service provider.
// It cannot be nil in the internal state.
// It is ignored if it is set to the empty string.
Auth *string
Auth *string `json:"auth"`
// Cert is the base64 encoded DER of an 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.
Cert *string
Cert *string `json:"cert"`
// Key is the base64 encoded DER of an 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.
Key *string
Key *string `json:"key"`
// EncryptedKey is the base64 encoded DER 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
EncryptedKey *string `json:"encrypted_key"`
// 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
KeyPassphrase *string `json:"key_passphrase"`
// PIAEncPreset is the encryption preset for
// Private Internet Access. It can be set to an
// empty string for other providers.
PIAEncPreset *string
PIAEncPreset *string `json:"pia_encryption_preset"`
// MSSFix is the value (1 to 10000) to set for the
// mssfix option for OpenVPN. It is ignored if set to 0.
// It cannot be nil in the internal state.
MSSFix *uint16
MSSFix *uint16 `json:"mssfix"`
// Interface is the OpenVPN device interface name.
// It cannot be an empty string in the internal state.
Interface string
Interface string `json:"interface"`
// ProcessUser is the OpenVPN process OS username
// to use. It cannot be empty in the internal state.
// It defaults to 'root'.
ProcessUser string
ProcessUser string `json:"process_user"`
// Verbosity is the OpenVPN verbosity level from 0 to 6.
// It cannot be nil in the internal state.
Verbosity *int
Verbosity *int `json:"verbosity"`
// Flags is a slice of additional flags to be passed
// to the OpenVPN program.
Flags []string
Flags []string `json:"flags"`
}
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{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, ", "))
validVersions := []string{openvpn.Openvpn25, openvpn.Openvpn26}
if err = validate.IsOneOf(o.Version, validVersions...); err != nil {
return fmt.Errorf("%w: %w", ErrOpenVPNVersionIsNotValid, err)
}
isCustom := vpnProvider == providers.Custom
@@ -163,7 +162,7 @@ func validateOpenVPNConfigFilepath(isCustom bool,
return fmt.Errorf("%w", ErrFilepathMissing)
}
err = helpers.FileExists(confFile)
err = validate.FileExists(confFile)
if err != nil {
return err
}
@@ -244,92 +243,92 @@ func validateOpenVPNEncryptedKey(vpnProvider,
func (o *OpenVPN) copy() (copied OpenVPN) {
return OpenVPN{
Version: o.Version,
User: helpers.CopyPointer(o.User),
Password: helpers.CopyPointer(o.Password),
ConfFile: helpers.CopyPointer(o.ConfFile),
Ciphers: helpers.CopySlice(o.Ciphers),
Auth: helpers.CopyPointer(o.Auth),
Cert: helpers.CopyPointer(o.Cert),
Key: helpers.CopyPointer(o.Key),
EncryptedKey: helpers.CopyPointer(o.EncryptedKey),
KeyPassphrase: helpers.CopyPointer(o.KeyPassphrase),
PIAEncPreset: helpers.CopyPointer(o.PIAEncPreset),
MSSFix: helpers.CopyPointer(o.MSSFix),
User: gosettings.CopyPointer(o.User),
Password: gosettings.CopyPointer(o.Password),
ConfFile: gosettings.CopyPointer(o.ConfFile),
Ciphers: gosettings.CopySlice(o.Ciphers),
Auth: gosettings.CopyPointer(o.Auth),
Cert: gosettings.CopyPointer(o.Cert),
Key: gosettings.CopyPointer(o.Key),
EncryptedKey: gosettings.CopyPointer(o.EncryptedKey),
KeyPassphrase: gosettings.CopyPointer(o.KeyPassphrase),
PIAEncPreset: gosettings.CopyPointer(o.PIAEncPreset),
MSSFix: gosettings.CopyPointer(o.MSSFix),
Interface: o.Interface,
ProcessUser: o.ProcessUser,
Verbosity: helpers.CopyPointer(o.Verbosity),
Flags: helpers.CopySlice(o.Flags),
Verbosity: gosettings.CopyPointer(o.Verbosity),
Flags: gosettings.CopySlice(o.Flags),
}
}
// mergeWith merges the other settings into any
// unset field of the receiver settings object.
func (o *OpenVPN) mergeWith(other OpenVPN) {
o.Version = helpers.MergeWithString(o.Version, other.Version)
o.User = helpers.MergeWithPointer(o.User, other.User)
o.Password = helpers.MergeWithPointer(o.Password, other.Password)
o.ConfFile = helpers.MergeWithPointer(o.ConfFile, other.ConfFile)
o.Ciphers = helpers.MergeSlices(o.Ciphers, other.Ciphers)
o.Auth = helpers.MergeWithPointer(o.Auth, other.Auth)
o.Cert = helpers.MergeWithPointer(o.Cert, other.Cert)
o.Key = helpers.MergeWithPointer(o.Key, other.Key)
o.EncryptedKey = helpers.MergeWithPointer(o.EncryptedKey, other.EncryptedKey)
o.KeyPassphrase = helpers.MergeWithPointer(o.KeyPassphrase, other.KeyPassphrase)
o.PIAEncPreset = helpers.MergeWithPointer(o.PIAEncPreset, other.PIAEncPreset)
o.MSSFix = helpers.MergeWithPointer(o.MSSFix, other.MSSFix)
o.Interface = helpers.MergeWithString(o.Interface, other.Interface)
o.ProcessUser = helpers.MergeWithString(o.ProcessUser, other.ProcessUser)
o.Verbosity = helpers.MergeWithPointer(o.Verbosity, other.Verbosity)
o.Flags = helpers.MergeSlices(o.Flags, other.Flags)
o.Version = gosettings.MergeWithString(o.Version, other.Version)
o.User = gosettings.MergeWithPointer(o.User, other.User)
o.Password = gosettings.MergeWithPointer(o.Password, other.Password)
o.ConfFile = gosettings.MergeWithPointer(o.ConfFile, other.ConfFile)
o.Ciphers = gosettings.MergeWithSlice(o.Ciphers, other.Ciphers)
o.Auth = gosettings.MergeWithPointer(o.Auth, other.Auth)
o.Cert = gosettings.MergeWithPointer(o.Cert, other.Cert)
o.Key = gosettings.MergeWithPointer(o.Key, other.Key)
o.EncryptedKey = gosettings.MergeWithPointer(o.EncryptedKey, other.EncryptedKey)
o.KeyPassphrase = gosettings.MergeWithPointer(o.KeyPassphrase, other.KeyPassphrase)
o.PIAEncPreset = gosettings.MergeWithPointer(o.PIAEncPreset, other.PIAEncPreset)
o.MSSFix = gosettings.MergeWithPointer(o.MSSFix, other.MSSFix)
o.Interface = gosettings.MergeWithString(o.Interface, other.Interface)
o.ProcessUser = gosettings.MergeWithString(o.ProcessUser, other.ProcessUser)
o.Verbosity = gosettings.MergeWithPointer(o.Verbosity, other.Verbosity)
o.Flags = gosettings.MergeWithSlice(o.Flags, other.Flags)
}
// overrideWith overrides fields of the receiver
// settings object with any field set in the other
// settings.
func (o *OpenVPN) overrideWith(other OpenVPN) {
o.Version = helpers.OverrideWithString(o.Version, other.Version)
o.User = helpers.OverrideWithPointer(o.User, other.User)
o.Password = helpers.OverrideWithPointer(o.Password, other.Password)
o.ConfFile = helpers.OverrideWithPointer(o.ConfFile, other.ConfFile)
o.Ciphers = helpers.OverrideWithSlice(o.Ciphers, other.Ciphers)
o.Auth = helpers.OverrideWithPointer(o.Auth, other.Auth)
o.Cert = helpers.OverrideWithPointer(o.Cert, other.Cert)
o.Key = helpers.OverrideWithPointer(o.Key, other.Key)
o.EncryptedKey = helpers.OverrideWithPointer(o.EncryptedKey, other.EncryptedKey)
o.KeyPassphrase = helpers.OverrideWithPointer(o.KeyPassphrase, other.KeyPassphrase)
o.PIAEncPreset = helpers.OverrideWithPointer(o.PIAEncPreset, other.PIAEncPreset)
o.MSSFix = helpers.OverrideWithPointer(o.MSSFix, other.MSSFix)
o.Interface = helpers.OverrideWithString(o.Interface, other.Interface)
o.ProcessUser = helpers.OverrideWithString(o.ProcessUser, other.ProcessUser)
o.Verbosity = helpers.OverrideWithPointer(o.Verbosity, other.Verbosity)
o.Flags = helpers.OverrideWithSlice(o.Flags, other.Flags)
o.Version = gosettings.OverrideWithString(o.Version, other.Version)
o.User = gosettings.OverrideWithPointer(o.User, other.User)
o.Password = gosettings.OverrideWithPointer(o.Password, other.Password)
o.ConfFile = gosettings.OverrideWithPointer(o.ConfFile, other.ConfFile)
o.Ciphers = gosettings.OverrideWithSlice(o.Ciphers, other.Ciphers)
o.Auth = gosettings.OverrideWithPointer(o.Auth, other.Auth)
o.Cert = gosettings.OverrideWithPointer(o.Cert, other.Cert)
o.Key = gosettings.OverrideWithPointer(o.Key, other.Key)
o.EncryptedKey = gosettings.OverrideWithPointer(o.EncryptedKey, other.EncryptedKey)
o.KeyPassphrase = gosettings.OverrideWithPointer(o.KeyPassphrase, other.KeyPassphrase)
o.PIAEncPreset = gosettings.OverrideWithPointer(o.PIAEncPreset, other.PIAEncPreset)
o.MSSFix = gosettings.OverrideWithPointer(o.MSSFix, other.MSSFix)
o.Interface = gosettings.OverrideWithString(o.Interface, other.Interface)
o.ProcessUser = gosettings.OverrideWithString(o.ProcessUser, other.ProcessUser)
o.Verbosity = gosettings.OverrideWithPointer(o.Verbosity, other.Verbosity)
o.Flags = gosettings.OverrideWithSlice(o.Flags, other.Flags)
}
func (o *OpenVPN) setDefaults(vpnProvider string) {
o.Version = helpers.DefaultString(o.Version, openvpn.Openvpn25)
o.User = helpers.DefaultPointer(o.User, "")
o.Version = gosettings.DefaultString(o.Version, openvpn.Openvpn25)
o.User = gosettings.DefaultPointer(o.User, "")
if vpnProvider == providers.Mullvad {
o.Password = helpers.DefaultPointer(o.Password, "m")
o.Password = gosettings.DefaultPointer(o.Password, "m")
} else {
o.Password = helpers.DefaultPointer(o.Password, "")
o.Password = gosettings.DefaultPointer(o.Password, "")
}
o.ConfFile = helpers.DefaultPointer(o.ConfFile, "")
o.Auth = helpers.DefaultPointer(o.Auth, "")
o.Cert = helpers.DefaultPointer(o.Cert, "")
o.Key = helpers.DefaultPointer(o.Key, "")
o.EncryptedKey = helpers.DefaultPointer(o.EncryptedKey, "")
o.KeyPassphrase = helpers.DefaultPointer(o.KeyPassphrase, "")
o.ConfFile = gosettings.DefaultPointer(o.ConfFile, "")
o.Auth = gosettings.DefaultPointer(o.Auth, "")
o.Cert = gosettings.DefaultPointer(o.Cert, "")
o.Key = gosettings.DefaultPointer(o.Key, "")
o.EncryptedKey = gosettings.DefaultPointer(o.EncryptedKey, "")
o.KeyPassphrase = gosettings.DefaultPointer(o.KeyPassphrase, "")
var defaultEncPreset string
if vpnProvider == providers.PrivateInternetAccess {
defaultEncPreset = presets.Strong
}
o.PIAEncPreset = helpers.DefaultPointer(o.PIAEncPreset, defaultEncPreset)
o.MSSFix = helpers.DefaultPointer(o.MSSFix, 0)
o.Interface = helpers.DefaultString(o.Interface, "tun0")
o.ProcessUser = helpers.DefaultString(o.ProcessUser, "root")
o.Verbosity = helpers.DefaultPointer(o.Verbosity, 1)
o.PIAEncPreset = gosettings.DefaultPointer(o.PIAEncPreset, defaultEncPreset)
o.MSSFix = gosettings.DefaultPointer(o.MSSFix, 0)
o.Interface = gosettings.DefaultString(o.Interface, "tun0")
o.ProcessUser = gosettings.DefaultString(o.ProcessUser, "root")
o.Verbosity = gosettings.DefaultPointer(o.Verbosity, 1)
}
func (o OpenVPN) String() string {
@@ -339,8 +338,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", gosettings.ObfuscateKey(*o.User))
node.Appendf("Password: %s", gosettings.ObfuscateKey(*o.Password))
if *o.ConfFile != "" {
node.Appendf("Custom configuration file: %s", *o.ConfFile)
@@ -355,16 +354,16 @@ func (o OpenVPN) toLinesNode() (node *gotree.Node) {
}
if *o.Cert != "" {
node.Appendf("Client crt: %s", helpers.ObfuscateData(*o.Cert))
node.Appendf("Client crt: %s", gosettings.ObfuscateKey(*o.Cert))
}
if *o.Key != "" {
node.Appendf("Client key: %s", helpers.ObfuscateData(*o.Key))
node.Appendf("Client key: %s", gosettings.ObfuscateKey(*o.Key))
}
if *o.EncryptedKey != "" {
node.Appendf("Encrypted key: %s (key passhrapse %s)",
helpers.ObfuscateData(*o.EncryptedKey), helpers.ObfuscatePassword(*o.KeyPassphrase))
gosettings.ObfuscateKey(*o.EncryptedKey), gosettings.ObfuscateKey(*o.KeyPassphrase))
}
if *o.PIAEncPreset != "" {

View File

@@ -6,6 +6,8 @@ import (
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/provider/privateinternetaccess/presets"
"github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/validate"
"github.com/qdm12/gotree"
)
@@ -14,25 +16,25 @@ type OpenVPNSelection struct {
// It can be set to an empty string to indicate to
// NOT use a custom configuration file.
// It cannot be nil in the internal state.
ConfFile *string
ConfFile *string `json:"config_file_path"`
// TCP is true if the OpenVPN protocol is TCP,
// and false for UDP.
// It cannot be nil in the internal state.
TCP *bool
TCP *bool `json:"tcp"`
// CustomPort is the OpenVPN server endpoint port.
// It can be set to 0 to indicate no custom port should
// be used. It cannot be nil in the internal state.
CustomPort *uint16 // HideMyAss, Mullvad, PIA, ProtonVPN, WeVPN, Windscribe
CustomPort *uint16 `json:"custom_port"`
// PIAEncPreset is the encryption preset for
// Private Internet Access. It can be set to an
// empty string for other providers.
PIAEncPreset *string
PIAEncPreset *string `json:"pia_encryption_preset"`
}
func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
// Validate ConfFile
if confFile := *o.ConfFile; confFile != "" {
err := helpers.FileExists(confFile)
err := validate.FileExists(confFile)
if err != nil {
return fmt.Errorf("configuration file: %w", err)
}
@@ -43,7 +45,6 @@ func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
providers.Ipvanish,
providers.Perfectprivacy,
providers.Privado,
providers.VPNUnlimited,
providers.Vyprvpn,
) {
return fmt.Errorf("%w: for VPN service provider %s",
@@ -54,7 +55,7 @@ func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
if *o.CustomPort != 0 {
switch vpnProvider {
// no restriction on port
case providers.Cyberghost, providers.HideMyAss,
case providers.Custom, providers.Cyberghost, providers.HideMyAss,
providers.Privatevpn, providers.Torguard:
// no custom port allowed
case providers.Expressvpn, providers.Fastestvpn,
@@ -97,16 +98,18 @@ func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
case providers.Windscribe:
allowedTCP = []uint16{21, 22, 80, 123, 143, 443, 587, 1194, 3306, 8080, 54783}
allowedUDP = []uint16{53, 80, 123, 443, 1194, 54783}
default:
panic(fmt.Sprintf("VPN provider %s has no registered allowed ports", vpnProvider))
}
if *o.TCP && !helpers.Uint16IsOneOf(*o.CustomPort, allowedTCP) {
return fmt.Errorf("%w: %d for VPN service provider %s; %s",
ErrOpenVPNCustomPortNotAllowed, o.CustomPort, vpnProvider,
helpers.PortChoicesOrString(allowedTCP))
} else if !*o.TCP && !helpers.Uint16IsOneOf(*o.CustomPort, allowedUDP) {
return fmt.Errorf("%w: %d for VPN service provider %s; %s",
ErrOpenVPNCustomPortNotAllowed, o.CustomPort, vpnProvider,
helpers.PortChoicesOrString(allowedUDP))
allowedPorts := allowedUDP
if *o.TCP {
allowedPorts = allowedTCP
}
err = validate.IsOneOf(*o.CustomPort, allowedPorts...)
if err != nil {
return fmt.Errorf("%w: for VPN service provider %s: %w",
ErrOpenVPNCustomPortNotAllowed, vpnProvider, err)
}
}
}
@@ -118,10 +121,8 @@ func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
presets.Normal,
presets.Strong,
}
if !helpers.IsOneOf(*o.PIAEncPreset, validEncryptionPresets...) {
return fmt.Errorf("%w: %s; valid presets are %s",
ErrOpenVPNEncryptionPresetNotValid, *o.PIAEncPreset,
helpers.ChoicesOrString(validEncryptionPresets))
if err = validate.IsOneOf(*o.PIAEncPreset, validEncryptionPresets...); err != nil {
return fmt.Errorf("%w: %w", ErrOpenVPNEncryptionPresetNotValid, err)
}
}
@@ -130,37 +131,37 @@ func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
func (o *OpenVPNSelection) copy() (copied OpenVPNSelection) {
return OpenVPNSelection{
ConfFile: helpers.CopyPointer(o.ConfFile),
TCP: helpers.CopyPointer(o.TCP),
CustomPort: helpers.CopyPointer(o.CustomPort),
PIAEncPreset: helpers.CopyPointer(o.PIAEncPreset),
ConfFile: gosettings.CopyPointer(o.ConfFile),
TCP: gosettings.CopyPointer(o.TCP),
CustomPort: gosettings.CopyPointer(o.CustomPort),
PIAEncPreset: gosettings.CopyPointer(o.PIAEncPreset),
}
}
func (o *OpenVPNSelection) mergeWith(other OpenVPNSelection) {
o.ConfFile = helpers.MergeWithPointer(o.ConfFile, other.ConfFile)
o.TCP = helpers.MergeWithPointer(o.TCP, other.TCP)
o.CustomPort = helpers.MergeWithPointer(o.CustomPort, other.CustomPort)
o.PIAEncPreset = helpers.MergeWithPointer(o.PIAEncPreset, other.PIAEncPreset)
o.ConfFile = gosettings.MergeWithPointer(o.ConfFile, other.ConfFile)
o.TCP = gosettings.MergeWithPointer(o.TCP, other.TCP)
o.CustomPort = gosettings.MergeWithPointer(o.CustomPort, other.CustomPort)
o.PIAEncPreset = gosettings.MergeWithPointer(o.PIAEncPreset, other.PIAEncPreset)
}
func (o *OpenVPNSelection) overrideWith(other OpenVPNSelection) {
o.ConfFile = helpers.OverrideWithPointer(o.ConfFile, other.ConfFile)
o.TCP = helpers.OverrideWithPointer(o.TCP, other.TCP)
o.CustomPort = helpers.OverrideWithPointer(o.CustomPort, other.CustomPort)
o.PIAEncPreset = helpers.OverrideWithPointer(o.PIAEncPreset, other.PIAEncPreset)
o.ConfFile = gosettings.OverrideWithPointer(o.ConfFile, other.ConfFile)
o.TCP = gosettings.OverrideWithPointer(o.TCP, other.TCP)
o.CustomPort = gosettings.OverrideWithPointer(o.CustomPort, other.CustomPort)
o.PIAEncPreset = gosettings.OverrideWithPointer(o.PIAEncPreset, other.PIAEncPreset)
}
func (o *OpenVPNSelection) setDefaults(vpnProvider string) {
o.ConfFile = helpers.DefaultPointer(o.ConfFile, "")
o.TCP = helpers.DefaultPointer(o.TCP, false)
o.CustomPort = helpers.DefaultPointer(o.CustomPort, 0)
o.ConfFile = gosettings.DefaultPointer(o.ConfFile, "")
o.TCP = gosettings.DefaultPointer(o.TCP, false)
o.CustomPort = gosettings.DefaultPointer(o.CustomPort, 0)
var defaultEncPreset string
if vpnProvider == providers.PrivateInternetAccess {
defaultEncPreset = presets.Strong
}
o.PIAEncPreset = helpers.DefaultPointer(o.PIAEncPreset, defaultEncPreset)
o.PIAEncPreset = gosettings.DefaultPointer(o.PIAEncPreset, defaultEncPreset)
}
func (o OpenVPNSelection) String() string {

View File

@@ -3,10 +3,10 @@ package settings
import (
"fmt"
"path/filepath"
"strings"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/validate"
"github.com/qdm12/gotree"
)
@@ -14,24 +14,42 @@ import (
type PortForwarding struct {
// Enabled is true if port forwarding should be activated.
// It cannot be nil for the internal state.
Enabled *bool
Enabled *bool `json:"enabled"`
// Provider is set to specify which custom port forwarding code
// should be used. This is especially necessary for the custom
// provider using Wireguard for a provider where Wireguard is not
// natively supported but custom port forwading code is available.
// It defaults to the empty string, meaning the current provider
// should be the one used for port forwarding.
// It cannot be nil for the internal state.
Provider *string `json:"provider"`
// Filepath is the port forwarding status file path
// to use. It can be the empty string to indicate not
// to write to a file. It cannot be nil for the
// internal state
Filepath *string
Filepath *string `json:"status_file_path"`
// ListeningPort is the port traffic would be redirected to from the
// forwarded port. The redirection is disabled if it is set to 0, which
// is its default as well.
ListeningPort *uint16 `json:"listening_port"`
}
func (p PortForwarding) validate(vpnProvider string) (err error) {
func (p PortForwarding) Validate(vpnProvider string) (err error) {
if !*p.Enabled {
return nil
}
// Validate Enabled
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, ", "))
// Validate current provider or custom provider specified
providerSelected := vpnProvider
if *p.Provider != "" {
providerSelected = *p.Provider
}
validProviders := []string{
providers.PrivateInternetAccess,
providers.Protonvpn,
}
if err = validate.IsOneOf(providerSelected, validProviders...); err != nil {
return fmt.Errorf("%w: %w", ErrPortForwardingEnabled, err)
}
// Validate Filepath
@@ -45,26 +63,34 @@ func (p PortForwarding) validate(vpnProvider string) (err error) {
return nil
}
func (p *PortForwarding) copy() (copied PortForwarding) {
func (p *PortForwarding) Copy() (copied PortForwarding) {
return PortForwarding{
Enabled: helpers.CopyPointer(p.Enabled),
Filepath: helpers.CopyPointer(p.Filepath),
Enabled: gosettings.CopyPointer(p.Enabled),
Provider: gosettings.CopyPointer(p.Provider),
Filepath: gosettings.CopyPointer(p.Filepath),
ListeningPort: gosettings.CopyPointer(p.ListeningPort),
}
}
func (p *PortForwarding) mergeWith(other PortForwarding) {
p.Enabled = helpers.MergeWithPointer(p.Enabled, other.Enabled)
p.Filepath = helpers.MergeWithPointer(p.Filepath, other.Filepath)
p.Enabled = gosettings.MergeWithPointer(p.Enabled, other.Enabled)
p.Provider = gosettings.MergeWithPointer(p.Provider, other.Provider)
p.Filepath = gosettings.MergeWithPointer(p.Filepath, other.Filepath)
p.ListeningPort = gosettings.MergeWithPointer(p.ListeningPort, other.ListeningPort)
}
func (p *PortForwarding) overrideWith(other PortForwarding) {
p.Enabled = helpers.OverrideWithPointer(p.Enabled, other.Enabled)
p.Filepath = helpers.OverrideWithPointer(p.Filepath, other.Filepath)
func (p *PortForwarding) OverrideWith(other PortForwarding) {
p.Enabled = gosettings.OverrideWithPointer(p.Enabled, other.Enabled)
p.Provider = gosettings.OverrideWithPointer(p.Provider, other.Provider)
p.Filepath = gosettings.OverrideWithPointer(p.Filepath, other.Filepath)
p.ListeningPort = gosettings.OverrideWithPointer(p.ListeningPort, other.ListeningPort)
}
func (p *PortForwarding) setDefaults() {
p.Enabled = helpers.DefaultPointer(p.Enabled, false)
p.Filepath = helpers.DefaultPointer(p.Filepath, "/tmp/gluetun/forwarded_port")
p.Enabled = gosettings.DefaultPointer(p.Enabled, false)
p.Provider = gosettings.DefaultPointer(p.Provider, "")
p.Filepath = gosettings.DefaultPointer(p.Filepath, "/tmp/gluetun/forwarded_port")
p.ListeningPort = gosettings.DefaultPointer(p.ListeningPort, 0)
}
func (p PortForwarding) String() string {
@@ -77,7 +103,18 @@ func (p PortForwarding) toLinesNode() (node *gotree.Node) {
}
node = gotree.New("Automatic port forwarding settings:")
node.Appendf("Enabled: yes")
listeningPort := "disabled"
if *p.ListeningPort != 0 {
listeningPort = fmt.Sprintf("%d", *p.ListeningPort)
}
node.Appendf("Redirection listening port: %s", listeningPort)
if *p.Provider == "" {
node.Appendf("Use port forwarding code for current provider")
} else {
node.Appendf("Use code for provider: %s", *p.Provider)
}
filepath := *p.Filepath
if filepath == "" {

View File

@@ -3,9 +3,10 @@ package settings
import (
"fmt"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/constants/vpn"
"github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/validate"
"github.com/qdm12/gotree"
)
@@ -13,12 +14,12 @@ import (
type Provider struct {
// Name is the VPN service provider name.
// It cannot be nil in the internal state.
Name *string
Name *string `json:"name"`
// ServerSelection is the settings to
// select the VPN server.
ServerSelection ServerSelection
ServerSelection ServerSelection `json:"server_selection"`
// PortForwarding is the settings about port forwarding.
PortForwarding PortForwarding
PortForwarding PortForwarding `json:"port_forwarding"`
}
// TODO v4 remove pointer for receiver (because of Surfshark).
@@ -34,13 +35,13 @@ func (p *Provider) validate(vpnType string, storage Storage) (err error) {
providers.Custom,
providers.Ivpn,
providers.Mullvad,
providers.Nordvpn,
providers.Surfshark,
providers.Windscribe,
}
}
if !helpers.IsOneOf(*p.Name, validNames...) {
return fmt.Errorf("%w for Wireguard: %q can only be one of %s",
ErrVPNProviderNameNotValid, *p.Name, helpers.ChoicesOrString(validNames))
if err = validate.IsOneOf(*p.Name, validNames...); err != nil {
return fmt.Errorf("%w for Wireguard: %w", ErrVPNProviderNameNotValid, err)
}
err = p.ServerSelection.validate(*p.Name, storage)
@@ -48,7 +49,7 @@ func (p *Provider) validate(vpnType string, storage Storage) (err error) {
return fmt.Errorf("server selection: %w", err)
}
err = p.PortForwarding.validate(*p.Name)
err = p.PortForwarding.Validate(*p.Name)
if err != nil {
return fmt.Errorf("port forwarding: %w", err)
}
@@ -58,26 +59,26 @@ func (p *Provider) validate(vpnType string, storage Storage) (err error) {
func (p *Provider) copy() (copied Provider) {
return Provider{
Name: helpers.CopyPointer(p.Name),
Name: gosettings.CopyPointer(p.Name),
ServerSelection: p.ServerSelection.copy(),
PortForwarding: p.PortForwarding.copy(),
PortForwarding: p.PortForwarding.Copy(),
}
}
func (p *Provider) mergeWith(other Provider) {
p.Name = helpers.MergeWithPointer(p.Name, other.Name)
p.Name = gosettings.MergeWithPointer(p.Name, other.Name)
p.ServerSelection.mergeWith(other.ServerSelection)
p.PortForwarding.mergeWith(other.PortForwarding)
}
func (p *Provider) overrideWith(other Provider) {
p.Name = helpers.OverrideWithPointer(p.Name, other.Name)
p.Name = gosettings.OverrideWithPointer(p.Name, other.Name)
p.ServerSelection.overrideWith(other.ServerSelection)
p.PortForwarding.overrideWith(other.PortForwarding)
p.PortForwarding.OverrideWith(other.PortForwarding)
}
func (p *Provider) setDefaults() {
p.Name = helpers.DefaultPointer(p.Name, providers.PrivateInternetAccess)
p.Name = gosettings.DefaultPointer(p.Name, providers.PrivateInternetAccess)
p.ServerSelection.setDefaults(*p.Name)
p.PortForwarding.setDefaults()
}

View File

@@ -5,7 +5,8 @@ import (
"path/filepath"
"time"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/publicip/api"
"github.com/qdm12/gosettings"
"github.com/qdm12/gotree"
)
@@ -21,6 +22,28 @@ type PublicIP struct {
// to write to a file. It cannot be nil for the
// internal state
IPFilepath *string
// API is the API name to use to fetch public IP information.
// It can be ipinfo or ip2location. It defaults to ipinfo.
API string
// APIToken is the token to use for the IP data service
// such as ipinfo.io. It can be the empty string to
// indicate not to use a token. It cannot be nil for the
// internal state.
APIToken *string
}
// UpdateWith deep copies the receiving settings, overrides the copy with
// fields set in the partialUpdate argument, validates the new settings
// and returns them if they are valid, or returns an error otherwise.
// In all cases, the receiving settings are unmodified.
func (p PublicIP) UpdateWith(partialUpdate PublicIP) (updatedSettings PublicIP, err error) {
updatedSettings = p.copy()
updatedSettings.overrideWith(partialUpdate)
err = updatedSettings.validate()
if err != nil {
return updatedSettings, fmt.Errorf("validating updated settings: %w", err)
}
return updatedSettings, nil
}
func (p PublicIP) validate() (err error) {
@@ -37,30 +60,43 @@ func (p PublicIP) validate() (err error) {
}
}
_, err = api.ParseProvider(p.API)
if err != nil {
return fmt.Errorf("API name: %w", err)
}
return nil
}
func (p *PublicIP) copy() (copied PublicIP) {
return PublicIP{
Period: helpers.CopyPointer(p.Period),
IPFilepath: helpers.CopyPointer(p.IPFilepath),
Period: gosettings.CopyPointer(p.Period),
IPFilepath: gosettings.CopyPointer(p.IPFilepath),
API: p.API,
APIToken: gosettings.CopyPointer(p.APIToken),
}
}
func (p *PublicIP) mergeWith(other PublicIP) {
p.Period = helpers.MergeWithPointer(p.Period, other.Period)
p.IPFilepath = helpers.MergeWithPointer(p.IPFilepath, other.IPFilepath)
p.Period = gosettings.MergeWithPointer(p.Period, other.Period)
p.IPFilepath = gosettings.MergeWithPointer(p.IPFilepath, other.IPFilepath)
p.API = gosettings.MergeWithString(p.API, other.API)
p.APIToken = gosettings.MergeWithPointer(p.APIToken, other.APIToken)
}
func (p *PublicIP) overrideWith(other PublicIP) {
p.Period = helpers.OverrideWithPointer(p.Period, other.Period)
p.IPFilepath = helpers.OverrideWithPointer(p.IPFilepath, other.IPFilepath)
p.Period = gosettings.OverrideWithPointer(p.Period, other.Period)
p.IPFilepath = gosettings.OverrideWithPointer(p.IPFilepath, other.IPFilepath)
p.API = gosettings.OverrideWithString(p.API, other.API)
p.APIToken = gosettings.OverrideWithPointer(p.APIToken, other.APIToken)
}
func (p *PublicIP) setDefaults() {
const defaultPeriod = 12 * time.Hour
p.Period = helpers.DefaultPointer(p.Period, defaultPeriod)
p.IPFilepath = helpers.DefaultPointer(p.IPFilepath, "/tmp/gluetun/ip")
p.Period = gosettings.DefaultPointer(p.Period, defaultPeriod)
p.IPFilepath = gosettings.DefaultPointer(p.IPFilepath, "/tmp/gluetun/ip")
p.API = gosettings.DefaultString(p.API, "ipinfo")
p.APIToken = gosettings.DefaultPointer(p.APIToken, "")
}
func (p PublicIP) String() string {
@@ -85,5 +121,11 @@ func (p PublicIP) toLinesNode() (node *gotree.Node) {
node.Appendf("IP file path: %s", *p.IPFilepath)
}
node.Appendf("Public IP data API: %s", p.API)
if *p.APIToken != "" {
node.Appendf("API token: %s", gosettings.ObfuscateKey(*p.APIToken))
}
return node
}

View File

@@ -6,7 +6,7 @@ import (
"os"
"strconv"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gosettings"
"github.com/qdm12/gotree"
)
@@ -43,29 +43,29 @@ func (c ControlServer) validate() (err error) {
func (c *ControlServer) copy() (copied ControlServer) {
return ControlServer{
Address: helpers.CopyPointer(c.Address),
Log: helpers.CopyPointer(c.Log),
Address: gosettings.CopyPointer(c.Address),
Log: gosettings.CopyPointer(c.Log),
}
}
// mergeWith merges the other settings into any
// unset field of the receiver settings object.
func (c *ControlServer) mergeWith(other ControlServer) {
c.Address = helpers.MergeWithPointer(c.Address, other.Address)
c.Log = helpers.MergeWithPointer(c.Log, other.Log)
c.Address = gosettings.MergeWithPointer(c.Address, other.Address)
c.Log = gosettings.MergeWithPointer(c.Log, other.Log)
}
// overrideWith overrides fields of the receiver
// settings object with any field set in the other
// settings.
func (c *ControlServer) overrideWith(other ControlServer) {
c.Address = helpers.OverrideWithPointer(c.Address, other.Address)
c.Log = helpers.OverrideWithPointer(c.Log, other.Log)
c.Address = gosettings.OverrideWithPointer(c.Address, other.Address)
c.Log = gosettings.OverrideWithPointer(c.Log, other.Log)
}
func (c *ControlServer) setDefaults() {
c.Address = helpers.DefaultPointer(c.Address, ":8000")
c.Log = helpers.DefaultPointer(c.Log, true)
c.Address = gosettings.DefaultPointer(c.Address, ":8000")
c.Log = gosettings.DefaultPointer(c.Log, true)
}
func (c ControlServer) String() string {
@@ -75,6 +75,6 @@ func (c ControlServer) String() string {
func (c ControlServer) toLinesNode() (node *gotree.Node) {
node = gotree.New("Control server settings:")
node.Appendf("Listening address: %s", *c.Address)
node.Appendf("Logging: %s", helpers.BoolPtrToYesNo(c.Log))
node.Appendf("Logging: %s", gosettings.BoolToYesNo(c.Log))
return node
}

View File

@@ -11,6 +11,8 @@ import (
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/constants/vpn"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/validate"
"github.com/qdm12/gotree"
)
@@ -18,59 +20,64 @@ type ServerSelection struct { //nolint:maligned
// VPN is the VPN type which can be 'openvpn'
// or 'wireguard'. It cannot be the empty string
// in the internal state.
VPN string
VPN string `json:"vpn"`
// TargetIP is the server endpoint IP address to use.
// It will override any IP address from the picked
// built-in server. It cannot be the empty value in the internal
// state, and can be set to the unspecified address to indicate
// there is not target IP address to use.
TargetIP netip.Addr
// Counties is the list of countries to filter VPN servers with.
Countries []string
TargetIP netip.Addr `json:"target_ip"`
// Countries is the list of countries to filter VPN servers with.
Countries []string `json:"countries"`
// Categories is the list of categories to filter VPN servers with.
Categories []string `json:"categories"`
// Regions is the list of regions to filter VPN servers with.
Regions []string
Regions []string `json:"regions"`
// Cities is the list of cities to filter VPN servers with.
Cities []string
Cities []string `json:"cities"`
// ISPs is the list of ISP names to filter VPN servers with.
ISPs []string
ISPs []string `json:"isps"`
// Names is the list of server names to filter VPN servers with.
Names []string
Names []string `json:"names"`
// Numbers is the list of server numbers to filter VPN servers with.
Numbers []uint16
Numbers []uint16 `json:"numbers"`
// Hostnames is the list of hostnames to filter VPN servers with.
Hostnames []string
Hostnames []string `json:"hostnames"`
// OwnedOnly is true if VPN provider servers that are not owned
// should be filtered. This is used with Mullvad.
OwnedOnly *bool
OwnedOnly *bool `json:"owned_only"`
// FreeOnly is true if VPN servers that are not free should
// be filtered. This is used with ProtonVPN and VPN Unlimited.
FreeOnly *bool
FreeOnly *bool `json:"free_only"`
// 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
PremiumOnly *bool `json:"premium_only"`
// StreamOnly is true if VPN servers not for streaming should
// be filtered. This is used with VPNUnlimited.
StreamOnly *bool
StreamOnly *bool `json:"stream_only"`
// MultiHopOnly is true if VPN servers that are not multihop
// should be filtered. This is used with Surfshark.
MultiHopOnly *bool
MultiHopOnly *bool `json:"multi_hop_only"`
// PortForwardOnly is true if VPN servers that don't support
// port forwarding should be filtered. This is used with PIA.
PortForwardOnly *bool `json:"port_forward_only"`
// OpenVPN contains settings to select OpenVPN servers
// and the final connection.
OpenVPN OpenVPNSelection
OpenVPN OpenVPNSelection `json:"openvpn"`
// Wireguard contains settings to select Wireguard servers
// and the final connection.
Wireguard WireguardSelection
Wireguard WireguardSelection `json:"wireguard"`
}
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")
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")
ErrPortForwardOnlyNotSupported = errors.New("port forwarding only filter is not supported")
ErrFreePremiumBothSet = errors.New("free only and premium only filters are both set")
)
func (ss *ServerSelection) validate(vpnServiceProvider string,
@@ -86,12 +93,17 @@ func (ss *ServerSelection) validate(vpnServiceProvider string,
return err // already wrapped error
}
// Retro-compatibility
switch vpnServiceProvider {
case providers.Nordvpn:
*ss = nordvpnRetroRegion(*ss, filterChoices.Regions, filterChoices.Countries)
case providers.Surfshark:
*ss = surfsharkRetroRegion(*ss)
}
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
return fmt.Errorf("for VPN service provider %s: %w", vpnServiceProvider, err)
}
if *ss.OwnedOnly &&
@@ -136,6 +148,15 @@ func (ss *ServerSelection) validate(vpnServiceProvider string,
ErrMultiHopOnlyNotSupported, vpnServiceProvider)
}
if *ss.PortForwardOnly &&
vpnServiceProvider != providers.PrivateInternetAccess {
// ProtonVPN also supports port forwarding, but on all their servers, so these
// don't have the port forwarding boolean field. As a consequence, we only allow
// the use of PortForwardOnly for Private Internet Access.
return fmt.Errorf("%w: for VPN service provider %s",
ErrPortForwardOnlyNotSupported, vpnServiceProvider)
}
if ss.VPN == vpn.OpenVPN {
err = ss.OpenVPN.validate(vpnServiceProvider)
if err != nil {
@@ -159,11 +180,14 @@ func getLocationFilterChoices(vpnServiceProvider string,
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)
newAndRetroRegions := append(filterChoices.Regions, validation.SurfsharkRetroLocChoices()...) //nolint:gocritic
err := validate.AreAllOneOfCaseInsensitive(ss.Regions, newAndRetroRegions)
if err != nil {
// Only return error comparing with newer regions, we don't want to confuse the user
// with the retro regions in the error message.
err = validate.AreAllOneOfCaseInsensitive(ss.Regions, filterChoices.Regions)
return models.FilterChoices{}, fmt.Errorf("%w: %w", ErrRegionNotValid, err)
}
*ss = surfsharkRetroRegion(*ss)
}
return filterChoices, nil
@@ -172,28 +196,39 @@ func getLocationFilterChoices(vpnServiceProvider string,
// 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, filterChoices models.FilterChoices) (err error) {
if err := helpers.AreAllOneOf(settings.Countries, filterChoices.Countries); err != nil {
return fmt.Errorf("%w: %s", ErrCountryNotValid, err)
err = validate.AreAllOneOfCaseInsensitive(settings.Countries, filterChoices.Countries)
if err != nil {
return fmt.Errorf("%w: %w", ErrCountryNotValid, err)
}
if err := helpers.AreAllOneOf(settings.Regions, filterChoices.Regions); err != nil {
return fmt.Errorf("%w: %s", ErrRegionNotValid, err)
err = validate.AreAllOneOfCaseInsensitive(settings.Regions, filterChoices.Regions)
if err != nil {
return fmt.Errorf("%w: %w", ErrRegionNotValid, err)
}
if err := helpers.AreAllOneOf(settings.Cities, filterChoices.Cities); err != nil {
return fmt.Errorf("%w: %s", ErrCityNotValid, err)
err = validate.AreAllOneOfCaseInsensitive(settings.Cities, filterChoices.Cities)
if err != nil {
return fmt.Errorf("%w: %w", ErrCityNotValid, err)
}
if err := helpers.AreAllOneOf(settings.ISPs, filterChoices.ISPs); err != nil {
return fmt.Errorf("%w: %s", ErrISPNotValid, err)
err = validate.AreAllOneOfCaseInsensitive(settings.ISPs, filterChoices.ISPs)
if err != nil {
return fmt.Errorf("%w: %w", ErrISPNotValid, err)
}
if err := helpers.AreAllOneOf(settings.Hostnames, filterChoices.Hostnames); err != nil {
return fmt.Errorf("%w: %s", ErrHostnameNotValid, err)
err = validate.AreAllOneOfCaseInsensitive(settings.Hostnames, filterChoices.Hostnames)
if err != nil {
return fmt.Errorf("%w: %w", ErrHostnameNotValid, err)
}
if err := helpers.AreAllOneOf(settings.Names, filterChoices.Names); err != nil {
return fmt.Errorf("%w: %s", ErrNameNotValid, err)
err = validate.AreAllOneOfCaseInsensitive(settings.Names, filterChoices.Names)
if err != nil {
return fmt.Errorf("%w: %w", ErrNameNotValid, err)
}
err = validate.AreAllOneOfCaseInsensitive(settings.Categories, filterChoices.Categories)
if err != nil {
return fmt.Errorf("%w: %w", ErrCategoryNotValid, err)
}
return nil
@@ -201,72 +236,79 @@ func validateServerFilters(settings ServerSelection, filterChoices models.Filter
func (ss *ServerSelection) copy() (copied ServerSelection) {
return ServerSelection{
VPN: ss.VPN,
TargetIP: ss.TargetIP,
Countries: helpers.CopySlice(ss.Countries),
Regions: helpers.CopySlice(ss.Regions),
Cities: helpers.CopySlice(ss.Cities),
ISPs: helpers.CopySlice(ss.ISPs),
Hostnames: helpers.CopySlice(ss.Hostnames),
Names: helpers.CopySlice(ss.Names),
Numbers: helpers.CopySlice(ss.Numbers),
OwnedOnly: helpers.CopyPointer(ss.OwnedOnly),
FreeOnly: helpers.CopyPointer(ss.FreeOnly),
PremiumOnly: helpers.CopyPointer(ss.PremiumOnly),
StreamOnly: helpers.CopyPointer(ss.StreamOnly),
MultiHopOnly: helpers.CopyPointer(ss.MultiHopOnly),
OpenVPN: ss.OpenVPN.copy(),
Wireguard: ss.Wireguard.copy(),
VPN: ss.VPN,
TargetIP: ss.TargetIP,
Countries: gosettings.CopySlice(ss.Countries),
Categories: gosettings.CopySlice(ss.Categories),
Regions: gosettings.CopySlice(ss.Regions),
Cities: gosettings.CopySlice(ss.Cities),
ISPs: gosettings.CopySlice(ss.ISPs),
Hostnames: gosettings.CopySlice(ss.Hostnames),
Names: gosettings.CopySlice(ss.Names),
Numbers: gosettings.CopySlice(ss.Numbers),
OwnedOnly: gosettings.CopyPointer(ss.OwnedOnly),
FreeOnly: gosettings.CopyPointer(ss.FreeOnly),
PremiumOnly: gosettings.CopyPointer(ss.PremiumOnly),
StreamOnly: gosettings.CopyPointer(ss.StreamOnly),
PortForwardOnly: gosettings.CopyPointer(ss.PortForwardOnly),
MultiHopOnly: gosettings.CopyPointer(ss.MultiHopOnly),
OpenVPN: ss.OpenVPN.copy(),
Wireguard: ss.Wireguard.copy(),
}
}
func (ss *ServerSelection) mergeWith(other ServerSelection) {
ss.VPN = helpers.MergeWithString(ss.VPN, other.VPN)
ss.TargetIP = helpers.MergeWithIP(ss.TargetIP, other.TargetIP)
ss.Countries = helpers.MergeSlices(ss.Countries, other.Countries)
ss.Regions = helpers.MergeSlices(ss.Regions, other.Regions)
ss.Cities = helpers.MergeSlices(ss.Cities, other.Cities)
ss.ISPs = helpers.MergeSlices(ss.ISPs, other.ISPs)
ss.Hostnames = helpers.MergeSlices(ss.Hostnames, other.Hostnames)
ss.Names = helpers.MergeSlices(ss.Names, other.Names)
ss.Numbers = helpers.MergeSlices(ss.Numbers, other.Numbers)
ss.OwnedOnly = helpers.MergeWithPointer(ss.OwnedOnly, other.OwnedOnly)
ss.FreeOnly = helpers.MergeWithPointer(ss.FreeOnly, other.FreeOnly)
ss.PremiumOnly = helpers.MergeWithPointer(ss.PremiumOnly, other.PremiumOnly)
ss.StreamOnly = helpers.MergeWithPointer(ss.StreamOnly, other.StreamOnly)
ss.MultiHopOnly = helpers.MergeWithPointer(ss.MultiHopOnly, other.MultiHopOnly)
ss.VPN = gosettings.MergeWithString(ss.VPN, other.VPN)
ss.TargetIP = gosettings.MergeWithValidator(ss.TargetIP, other.TargetIP)
ss.Countries = gosettings.MergeWithSlice(ss.Countries, other.Countries)
ss.Categories = gosettings.MergeWithSlice(ss.Categories, other.Categories)
ss.Regions = gosettings.MergeWithSlice(ss.Regions, other.Regions)
ss.Cities = gosettings.MergeWithSlice(ss.Cities, other.Cities)
ss.ISPs = gosettings.MergeWithSlice(ss.ISPs, other.ISPs)
ss.Hostnames = gosettings.MergeWithSlice(ss.Hostnames, other.Hostnames)
ss.Names = gosettings.MergeWithSlice(ss.Names, other.Names)
ss.Numbers = gosettings.MergeWithSlice(ss.Numbers, other.Numbers)
ss.OwnedOnly = gosettings.MergeWithPointer(ss.OwnedOnly, other.OwnedOnly)
ss.FreeOnly = gosettings.MergeWithPointer(ss.FreeOnly, other.FreeOnly)
ss.PremiumOnly = gosettings.MergeWithPointer(ss.PremiumOnly, other.PremiumOnly)
ss.StreamOnly = gosettings.MergeWithPointer(ss.StreamOnly, other.StreamOnly)
ss.MultiHopOnly = gosettings.MergeWithPointer(ss.MultiHopOnly, other.MultiHopOnly)
ss.PortForwardOnly = gosettings.MergeWithPointer(ss.PortForwardOnly, other.PortForwardOnly)
ss.OpenVPN.mergeWith(other.OpenVPN)
ss.Wireguard.mergeWith(other.Wireguard)
}
func (ss *ServerSelection) overrideWith(other ServerSelection) {
ss.VPN = helpers.OverrideWithString(ss.VPN, other.VPN)
ss.TargetIP = helpers.OverrideWithIP(ss.TargetIP, other.TargetIP)
ss.Countries = helpers.OverrideWithSlice(ss.Countries, other.Countries)
ss.Regions = helpers.OverrideWithSlice(ss.Regions, other.Regions)
ss.Cities = helpers.OverrideWithSlice(ss.Cities, other.Cities)
ss.ISPs = helpers.OverrideWithSlice(ss.ISPs, other.ISPs)
ss.Hostnames = helpers.OverrideWithSlice(ss.Hostnames, other.Hostnames)
ss.Names = helpers.OverrideWithSlice(ss.Names, other.Names)
ss.Numbers = helpers.OverrideWithSlice(ss.Numbers, other.Numbers)
ss.OwnedOnly = helpers.OverrideWithPointer(ss.OwnedOnly, other.OwnedOnly)
ss.FreeOnly = helpers.OverrideWithPointer(ss.FreeOnly, other.FreeOnly)
ss.PremiumOnly = helpers.OverrideWithPointer(ss.PremiumOnly, other.PremiumOnly)
ss.StreamOnly = helpers.OverrideWithPointer(ss.StreamOnly, other.StreamOnly)
ss.MultiHopOnly = helpers.OverrideWithPointer(ss.MultiHopOnly, other.MultiHopOnly)
ss.VPN = gosettings.OverrideWithString(ss.VPN, other.VPN)
ss.TargetIP = gosettings.OverrideWithValidator(ss.TargetIP, other.TargetIP)
ss.Countries = gosettings.OverrideWithSlice(ss.Countries, other.Countries)
ss.Categories = gosettings.OverrideWithSlice(ss.Categories, other.Categories)
ss.Regions = gosettings.OverrideWithSlice(ss.Regions, other.Regions)
ss.Cities = gosettings.OverrideWithSlice(ss.Cities, other.Cities)
ss.ISPs = gosettings.OverrideWithSlice(ss.ISPs, other.ISPs)
ss.Hostnames = gosettings.OverrideWithSlice(ss.Hostnames, other.Hostnames)
ss.Names = gosettings.OverrideWithSlice(ss.Names, other.Names)
ss.Numbers = gosettings.OverrideWithSlice(ss.Numbers, other.Numbers)
ss.OwnedOnly = gosettings.OverrideWithPointer(ss.OwnedOnly, other.OwnedOnly)
ss.FreeOnly = gosettings.OverrideWithPointer(ss.FreeOnly, other.FreeOnly)
ss.PremiumOnly = gosettings.OverrideWithPointer(ss.PremiumOnly, other.PremiumOnly)
ss.StreamOnly = gosettings.OverrideWithPointer(ss.StreamOnly, other.StreamOnly)
ss.MultiHopOnly = gosettings.OverrideWithPointer(ss.MultiHopOnly, other.MultiHopOnly)
ss.PortForwardOnly = gosettings.OverrideWithPointer(ss.PortForwardOnly, other.PortForwardOnly)
ss.OpenVPN.overrideWith(other.OpenVPN)
ss.Wireguard.overrideWith(other.Wireguard)
}
func (ss *ServerSelection) setDefaults(vpnProvider string) {
ss.VPN = helpers.DefaultString(ss.VPN, vpn.OpenVPN)
ss.TargetIP = helpers.DefaultIP(ss.TargetIP, netip.IPv4Unspecified())
ss.OwnedOnly = helpers.DefaultPointer(ss.OwnedOnly, false)
ss.FreeOnly = helpers.DefaultPointer(ss.FreeOnly, false)
ss.PremiumOnly = helpers.DefaultPointer(ss.PremiumOnly, false)
ss.StreamOnly = helpers.DefaultPointer(ss.StreamOnly, false)
ss.MultiHopOnly = helpers.DefaultPointer(ss.MultiHopOnly, false)
ss.VPN = gosettings.DefaultString(ss.VPN, vpn.OpenVPN)
ss.TargetIP = gosettings.DefaultValidator(ss.TargetIP, netip.IPv4Unspecified())
ss.OwnedOnly = gosettings.DefaultPointer(ss.OwnedOnly, false)
ss.FreeOnly = gosettings.DefaultPointer(ss.FreeOnly, false)
ss.PremiumOnly = gosettings.DefaultPointer(ss.PremiumOnly, false)
ss.StreamOnly = gosettings.DefaultPointer(ss.StreamOnly, false)
ss.MultiHopOnly = gosettings.DefaultPointer(ss.MultiHopOnly, false)
ss.PortForwardOnly = gosettings.DefaultPointer(ss.PortForwardOnly, false)
ss.OpenVPN.setDefaults(vpnProvider)
ss.Wireguard.setDefaults()
}
@@ -286,6 +328,10 @@ func (ss ServerSelection) toLinesNode() (node *gotree.Node) {
node.Appendf("Countries: %s", strings.Join(ss.Countries, ", "))
}
if len(ss.Categories) > 0 {
node.Appendf("Categories: %s", strings.Join(ss.Categories, ", "))
}
if len(ss.Regions) > 0 {
node.Appendf("Regions: %s", strings.Join(ss.Regions, ", "))
}
@@ -333,6 +379,10 @@ func (ss ServerSelection) toLinesNode() (node *gotree.Node) {
node.Appendf("Multi-hop only servers: yes")
}
if *ss.PortForwardOnly {
node.Appendf("Port forwarding only servers: yes")
}
if ss.VPN == vpn.OpenVPN {
node.AppendNode(ss.OpenVPN.toLinesNode())
} else {

View File

@@ -4,7 +4,6 @@ import (
"fmt"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/constants/openvpn"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/constants/vpn"
"github.com/qdm12/gluetun/internal/models"
@@ -170,27 +169,14 @@ func (s Settings) Warnings() (warnings []string) {
if helpers.IsOneOf(*s.VPN.Provider.Name, providers.SlickVPN) &&
s.VPN.Type == vpn.OpenVPN {
if s.VPN.OpenVPN.Version == openvpn.Openvpn24 {
warnings = append(warnings, "OpenVPN 2.4 uses OpenSSL 1.1.1 "+
"which allows the usage of weak security in today's standards. "+
"This can be ok if good security is enforced by the VPN provider. "+
"However, "+*s.VPN.Provider.Name+" uses weak security so you should use "+
"OpenVPN 2.5 to enforce good security practices.")
} else {
warnings = append(warnings, "OpenVPN 2.5 uses OpenSSL 3 "+
"which prohibits the usage of weak security in today's standards. "+
*s.VPN.Provider.Name+" uses weak security which is out "+
"of Gluetun's control so the only workaround is to allow such weaknesses "+
`using the OpenVPN option tls-cipher "DEFAULT:@SECLEVEL=0". `+
"You might want to reach to your provider so they upgrade their certificates. "+
"Once this is done, you will have to let the Gluetun maintainers know "+
"by creating an issue, attaching the new certificate and we will update Gluetun.")
}
}
if s.VPN.OpenVPN.Version == openvpn.Openvpn24 {
warnings = append(warnings, "OpenVPN 2.4 will be removed in release v3.34.0 (around June 2023). "+
"Please create an issue if you have a compelling reason to keep it.")
warnings = append(warnings, "OpenVPN 2.5 uses OpenSSL 3 "+
"which prohibits the usage of weak security in today's standards. "+
*s.VPN.Provider.Name+" uses weak security which is out "+
"of Gluetun's control so the only workaround is to allow such weaknesses "+
`using the OpenVPN option tls-cipher "DEFAULT:@SECLEVEL=0". `+
"You might want to reach to your provider so they upgrade their certificates. "+
"Once this is done, you will have to let the Gluetun maintainers know "+
"by creating an issue, attaching the new certificate and we will update Gluetun.")
}
return warnings

View File

@@ -38,8 +38,8 @@ func Test_Settings_String(t *testing.T) {
| ├── Run OpenVPN as: root
| └── Verbosity level: 1
├── DNS settings:
| ├── DNS server address to use: 127.0.0.1
| ├── Keep existing nameserver(s): no
| ├── DNS server address to use: 127.0.0.1
| └── DNS over TLS settings:
| ├── Enabled: yes
| ├── Update period: every 24h0m0s
@@ -84,7 +84,8 @@ func Test_Settings_String(t *testing.T) {
| └── Process GID: 1000
├── Public IP settings:
| ├── Fetching: every 12h0m0s
| ── IP file path: /tmp/gluetun/ip
| ── IP file path: /tmp/gluetun/ip
| └── Public IP data API: ipinfo
└── Version settings:
└── Enabled: yes`,
},

View File

@@ -1,7 +1,7 @@
package settings
import (
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gosettings"
"github.com/qdm12/gotree"
"github.com/qdm12/ss-server/pkg/tcpudp"
)
@@ -21,7 +21,7 @@ func (s Shadowsocks) validate() (err error) {
func (s *Shadowsocks) copy() (copied Shadowsocks) {
return Shadowsocks{
Enabled: helpers.CopyPointer(s.Enabled),
Enabled: gosettings.CopyPointer(s.Enabled),
Settings: s.Settings.Copy(),
}
}
@@ -29,20 +29,20 @@ func (s *Shadowsocks) copy() (copied Shadowsocks) {
// mergeWith merges the other settings into any
// unset field of the receiver settings object.
func (s *Shadowsocks) mergeWith(other Shadowsocks) {
s.Enabled = helpers.MergeWithPointer(s.Enabled, other.Enabled)
s.Settings.MergeWith(other.Settings)
s.Enabled = gosettings.MergeWithPointer(s.Enabled, other.Enabled)
s.Settings = s.Settings.MergeWith(other.Settings)
}
// overrideWith overrides fields of the receiver
// settings object with any field set in the other
// settings.
func (s *Shadowsocks) overrideWith(other Shadowsocks) {
s.Enabled = helpers.OverrideWithPointer(s.Enabled, other.Enabled)
s.Enabled = gosettings.OverrideWithPointer(s.Enabled, other.Enabled)
s.Settings.OverrideWith(other.Settings)
}
func (s *Shadowsocks) setDefaults() {
s.Enabled = helpers.DefaultPointer(s.Enabled, false)
s.Enabled = gosettings.DefaultPointer(s.Enabled, false)
s.Settings.SetDefaults()
}
@@ -53,16 +53,16 @@ func (s Shadowsocks) String() string {
func (s Shadowsocks) toLinesNode() (node *gotree.Node) {
node = gotree.New("Shadowsocks server settings:")
node.Appendf("Enabled: %s", helpers.BoolPtrToYesNo(s.Enabled))
node.Appendf("Enabled: %s", gosettings.BoolToYesNo(s.Enabled))
if !*s.Enabled {
return node
}
// TODO have ToLinesNode in qdm12/ss-server
node.Appendf("Listening address: %s", s.Address)
node.Appendf("Listening address: %s", *s.Address)
node.Appendf("Cipher: %s", s.CipherName)
node.Appendf("Password: %s", helpers.ObfuscatePassword(*s.Password))
node.Appendf("Log addresses: %s", helpers.BoolPtrToYesNo(s.LogAddresses))
node.Appendf("Password: %s", gosettings.ObfuscateKey(*s.Password))
node.Appendf("Log addresses: %s", gosettings.BoolToYesNo(s.LogAddresses))
return node
}

View File

@@ -1,7 +1,7 @@
package settings
import (
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gosettings"
"github.com/qdm12/gotree"
)
@@ -19,28 +19,28 @@ func (s System) validate() (err error) {
func (s *System) copy() (copied System) {
return System{
PUID: helpers.CopyPointer(s.PUID),
PGID: helpers.CopyPointer(s.PGID),
PUID: gosettings.CopyPointer(s.PUID),
PGID: gosettings.CopyPointer(s.PGID),
Timezone: s.Timezone,
}
}
func (s *System) mergeWith(other System) {
s.PUID = helpers.MergeWithPointer(s.PUID, other.PUID)
s.PGID = helpers.MergeWithPointer(s.PGID, other.PGID)
s.Timezone = helpers.MergeWithString(s.Timezone, other.Timezone)
s.PUID = gosettings.MergeWithPointer(s.PUID, other.PUID)
s.PGID = gosettings.MergeWithPointer(s.PGID, other.PGID)
s.Timezone = gosettings.MergeWithString(s.Timezone, other.Timezone)
}
func (s *System) overrideWith(other System) {
s.PUID = helpers.OverrideWithPointer(s.PUID, other.PUID)
s.PGID = helpers.OverrideWithPointer(s.PGID, other.PGID)
s.Timezone = helpers.OverrideWithString(s.Timezone, other.Timezone)
s.PUID = gosettings.OverrideWithPointer(s.PUID, other.PUID)
s.PGID = gosettings.OverrideWithPointer(s.PGID, other.PGID)
s.Timezone = gosettings.OverrideWithString(s.Timezone, other.Timezone)
}
func (s *System) setDefaults() {
const defaultID = 1000
s.PUID = helpers.DefaultPointer(s.PUID, defaultID)
s.PGID = helpers.DefaultPointer(s.PGID, defaultID)
s.PUID = gosettings.DefaultPointer(s.PUID, defaultID)
s.PGID = gosettings.DefaultPointer(s.PGID, defaultID)
}
func (s System) String() string {

View File

@@ -7,20 +7,20 @@ import (
"github.com/qdm12/dns/pkg/provider"
"github.com/qdm12/dns/pkg/unbound"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gosettings"
"github.com/qdm12/gotree"
)
// Unbound is settings for the Unbound program.
type Unbound struct {
Providers []string
Caching *bool
IPv6 *bool
VerbosityLevel *uint8
VerbosityDetailsLevel *uint8
ValidationLogLevel *uint8
Username string
Allowed []netip.Prefix
Providers []string `json:"providers"`
Caching *bool `json:"caching"`
IPv6 *bool `json:"ipv6"`
VerbosityLevel *uint8 `json:"verbosity_level"`
VerbosityDetailsLevel *uint8 `json:"verbosity_details_level"`
ValidationLogLevel *uint8 `json:"validation_log_level"`
Username string `json:"username"`
Allowed []netip.Prefix `json:"allowed"`
}
func (u *Unbound) setDefaults() {
@@ -30,17 +30,17 @@ func (u *Unbound) setDefaults() {
}
}
u.Caching = helpers.DefaultPointer(u.Caching, true)
u.IPv6 = helpers.DefaultPointer(u.IPv6, false)
u.Caching = gosettings.DefaultPointer(u.Caching, true)
u.IPv6 = gosettings.DefaultPointer(u.IPv6, false)
const defaultVerbosityLevel = 1
u.VerbosityLevel = helpers.DefaultPointer(u.VerbosityLevel, defaultVerbosityLevel)
u.VerbosityLevel = gosettings.DefaultPointer(u.VerbosityLevel, defaultVerbosityLevel)
const defaultVerbosityDetailsLevel = 0
u.VerbosityDetailsLevel = helpers.DefaultPointer(u.VerbosityDetailsLevel, defaultVerbosityDetailsLevel)
u.VerbosityDetailsLevel = gosettings.DefaultPointer(u.VerbosityDetailsLevel, defaultVerbosityDetailsLevel)
const defaultValidationLogLevel = 0
u.ValidationLogLevel = helpers.DefaultPointer(u.ValidationLogLevel, defaultValidationLogLevel)
u.ValidationLogLevel = gosettings.DefaultPointer(u.ValidationLogLevel, defaultValidationLogLevel)
if u.Allowed == nil {
u.Allowed = []netip.Prefix{
@@ -49,7 +49,7 @@ func (u *Unbound) setDefaults() {
}
}
u.Username = helpers.DefaultString(u.Username, "root")
u.Username = gosettings.DefaultString(u.Username, "root")
}
var (
@@ -94,37 +94,37 @@ func (u Unbound) validate() (err error) {
func (u Unbound) copy() (copied Unbound) {
return Unbound{
Providers: helpers.CopySlice(u.Providers),
Caching: helpers.CopyPointer(u.Caching),
IPv6: helpers.CopyPointer(u.IPv6),
VerbosityLevel: helpers.CopyPointer(u.VerbosityLevel),
VerbosityDetailsLevel: helpers.CopyPointer(u.VerbosityDetailsLevel),
ValidationLogLevel: helpers.CopyPointer(u.ValidationLogLevel),
Providers: gosettings.CopySlice(u.Providers),
Caching: gosettings.CopyPointer(u.Caching),
IPv6: gosettings.CopyPointer(u.IPv6),
VerbosityLevel: gosettings.CopyPointer(u.VerbosityLevel),
VerbosityDetailsLevel: gosettings.CopyPointer(u.VerbosityDetailsLevel),
ValidationLogLevel: gosettings.CopyPointer(u.ValidationLogLevel),
Username: u.Username,
Allowed: helpers.CopySlice(u.Allowed),
Allowed: gosettings.CopySlice(u.Allowed),
}
}
func (u *Unbound) mergeWith(other Unbound) {
u.Providers = helpers.MergeSlices(u.Providers, other.Providers)
u.Caching = helpers.MergeWithPointer(u.Caching, other.Caching)
u.IPv6 = helpers.MergeWithPointer(u.IPv6, other.IPv6)
u.VerbosityLevel = helpers.MergeWithPointer(u.VerbosityLevel, other.VerbosityLevel)
u.VerbosityDetailsLevel = helpers.MergeWithPointer(u.VerbosityDetailsLevel, other.VerbosityDetailsLevel)
u.ValidationLogLevel = helpers.MergeWithPointer(u.ValidationLogLevel, other.ValidationLogLevel)
u.Username = helpers.MergeWithString(u.Username, other.Username)
u.Allowed = helpers.MergeSlices(u.Allowed, other.Allowed)
u.Providers = gosettings.MergeWithSlice(u.Providers, other.Providers)
u.Caching = gosettings.MergeWithPointer(u.Caching, other.Caching)
u.IPv6 = gosettings.MergeWithPointer(u.IPv6, other.IPv6)
u.VerbosityLevel = gosettings.MergeWithPointer(u.VerbosityLevel, other.VerbosityLevel)
u.VerbosityDetailsLevel = gosettings.MergeWithPointer(u.VerbosityDetailsLevel, other.VerbosityDetailsLevel)
u.ValidationLogLevel = gosettings.MergeWithPointer(u.ValidationLogLevel, other.ValidationLogLevel)
u.Username = gosettings.MergeWithString(u.Username, other.Username)
u.Allowed = gosettings.MergeWithSlice(u.Allowed, other.Allowed)
}
func (u *Unbound) overrideWith(other Unbound) {
u.Providers = helpers.OverrideWithSlice(u.Providers, other.Providers)
u.Caching = helpers.OverrideWithPointer(u.Caching, other.Caching)
u.IPv6 = helpers.OverrideWithPointer(u.IPv6, other.IPv6)
u.VerbosityLevel = helpers.OverrideWithPointer(u.VerbosityLevel, other.VerbosityLevel)
u.VerbosityDetailsLevel = helpers.OverrideWithPointer(u.VerbosityDetailsLevel, other.VerbosityDetailsLevel)
u.ValidationLogLevel = helpers.OverrideWithPointer(u.ValidationLogLevel, other.ValidationLogLevel)
u.Username = helpers.OverrideWithString(u.Username, other.Username)
u.Allowed = helpers.OverrideWithSlice(u.Allowed, other.Allowed)
u.Providers = gosettings.OverrideWithSlice(u.Providers, other.Providers)
u.Caching = gosettings.OverrideWithPointer(u.Caching, other.Caching)
u.IPv6 = gosettings.OverrideWithPointer(u.IPv6, other.IPv6)
u.VerbosityLevel = gosettings.OverrideWithPointer(u.VerbosityLevel, other.VerbosityLevel)
u.VerbosityDetailsLevel = gosettings.OverrideWithPointer(u.VerbosityDetailsLevel, other.VerbosityDetailsLevel)
u.ValidationLogLevel = gosettings.OverrideWithPointer(u.ValidationLogLevel, other.ValidationLogLevel)
u.Username = gosettings.OverrideWithString(u.Username, other.Username)
u.Allowed = gosettings.OverrideWithSlice(u.Allowed, other.Allowed)
}
func (u Unbound) ToUnboundFormat() (settings unbound.Settings, err error) {
@@ -186,8 +186,8 @@ func (u Unbound) toLinesNode() (node *gotree.Node) {
authServers.Appendf(provider)
}
node.Appendf("Caching: %s", helpers.BoolPtrToYesNo(u.Caching))
node.Appendf("IPv6: %s", helpers.BoolPtrToYesNo(u.IPv6))
node.Appendf("Caching: %s", gosettings.BoolToYesNo(u.Caching))
node.Appendf("IPv6: %s", gosettings.BoolToYesNo(u.IPv6))
node.Appendf("Verbosity level: %d", *u.VerbosityLevel)
node.Appendf("Verbosity details level: %d", *u.VerbosityDetailsLevel)
node.Appendf("Validation log level: %d", *u.ValidationLogLevel)

View File

@@ -29,9 +29,9 @@ func Test_Unbound_JSON(t *testing.T) {
b, err := json.Marshal(settings)
require.NoError(t, err)
const expected = `{"Providers":["cloudflare"],"Caching":true,"IPv6":false,` +
`"VerbosityLevel":1,"VerbosityDetailsLevel":null,"ValidationLogLevel":0,` +
`"Username":"user","Allowed":["0.0.0.0/0","::/0"]}`
const expected = `{"providers":["cloudflare"],"caching":true,"ipv6":false,` +
`"verbosity_level":1,"verbosity_details_level":null,"validation_log_level":0,` +
`"username":"user","allowed":["0.0.0.0/0","::/0"]}`
assert.Equal(t, expected, string(b))

View File

@@ -5,8 +5,9 @@ import (
"strings"
"time"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/validate"
"github.com/qdm12/gotree"
)
@@ -45,16 +46,9 @@ func (u Updater) Validate() (err error) {
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: %q can only be one of %s",
ErrVPNProviderNameNotValid, provider, helpers.ChoicesOrString(validProviders))
err = validate.IsOneOf(provider, validProviders...)
if err != nil {
return fmt.Errorf("%w: %w", ErrVPNProviderNameNotValid, err)
}
}
@@ -63,35 +57,35 @@ func (u Updater) Validate() (err error) {
func (u *Updater) copy() (copied Updater) {
return Updater{
Period: helpers.CopyPointer(u.Period),
Period: gosettings.CopyPointer(u.Period),
DNSAddress: u.DNSAddress,
MinRatio: u.MinRatio,
Providers: helpers.CopySlice(u.Providers),
Providers: gosettings.CopySlice(u.Providers),
}
}
// mergeWith merges the other settings into any
// unset field of the receiver settings object.
func (u *Updater) mergeWith(other Updater) {
u.Period = helpers.MergeWithPointer(u.Period, other.Period)
u.DNSAddress = helpers.MergeWithString(u.DNSAddress, other.DNSAddress)
u.MinRatio = helpers.MergeWithNumber(u.MinRatio, other.MinRatio)
u.Providers = helpers.MergeSlices(u.Providers, other.Providers)
u.Period = gosettings.MergeWithPointer(u.Period, other.Period)
u.DNSAddress = gosettings.MergeWithString(u.DNSAddress, other.DNSAddress)
u.MinRatio = gosettings.MergeWithNumber(u.MinRatio, other.MinRatio)
u.Providers = gosettings.MergeWithSlice(u.Providers, other.Providers)
}
// overrideWith overrides fields of the receiver
// settings object with any field set in the other
// settings.
func (u *Updater) overrideWith(other Updater) {
u.Period = helpers.OverrideWithPointer(u.Period, other.Period)
u.DNSAddress = helpers.OverrideWithString(u.DNSAddress, other.DNSAddress)
u.MinRatio = helpers.OverrideWithNumber(u.MinRatio, other.MinRatio)
u.Providers = helpers.OverrideWithSlice(u.Providers, other.Providers)
u.Period = gosettings.OverrideWithPointer(u.Period, other.Period)
u.DNSAddress = gosettings.OverrideWithString(u.DNSAddress, other.DNSAddress)
u.MinRatio = gosettings.OverrideWithNumber(u.MinRatio, other.MinRatio)
u.Providers = gosettings.OverrideWithSlice(u.Providers, other.Providers)
}
func (u *Updater) SetDefaults(vpnProvider string) {
u.Period = helpers.DefaultPointer(u.Period, 0)
u.DNSAddress = helpers.DefaultString(u.DNSAddress, "1.1.1.1:53")
u.Period = gosettings.DefaultPointer(u.Period, 0)
u.DNSAddress = gosettings.DefaultString(u.DNSAddress, "1.1.1.1:53")
if u.MinRatio == 0 {
const defaultMinRatio = 0.8

View File

@@ -33,6 +33,28 @@ func ExtractCountries(servers []models.Server) (values []string) {
return values
}
func ExtractCategories(servers []models.Server) (values []string) {
seen := make(map[string]struct{}, len(servers))
values = make([]string, 0, len(servers))
for _, server := range servers {
categories := server.Categories
if len(categories) == 0 {
continue
}
for _, value := range categories {
_, 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))

View File

@@ -10,6 +10,9 @@ func SurfsharkRetroLocChoices() (choices []string) {
choices = make([]string, 0, len(locationData))
seen := make(map[string]struct{}, len(locationData))
for _, data := range locationData {
if data.RetroLoc == "" {
continue
}
if _, ok := seen[data.RetroLoc]; ok {
continue
}

View File

@@ -1,7 +1,7 @@
package settings
import (
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gosettings"
"github.com/qdm12/gotree"
)
@@ -19,25 +19,25 @@ func (v Version) validate() (err error) {
func (v *Version) copy() (copied Version) {
return Version{
Enabled: helpers.CopyPointer(v.Enabled),
Enabled: gosettings.CopyPointer(v.Enabled),
}
}
// mergeWith merges the other settings into any
// unset field of the receiver settings object.
func (v *Version) mergeWith(other Version) {
v.Enabled = helpers.MergeWithPointer(v.Enabled, other.Enabled)
v.Enabled = gosettings.MergeWithPointer(v.Enabled, other.Enabled)
}
// overrideWith overrides fields of the receiver
// settings object with any field set in the other
// settings.
func (v *Version) overrideWith(other Version) {
v.Enabled = helpers.OverrideWithPointer(v.Enabled, other.Enabled)
v.Enabled = gosettings.OverrideWithPointer(v.Enabled, other.Enabled)
}
func (v *Version) setDefaults() {
v.Enabled = helpers.DefaultPointer(v.Enabled, true)
v.Enabled = gosettings.DefaultPointer(v.Enabled, true)
}
func (v Version) String() string {
@@ -47,7 +47,7 @@ func (v Version) String() string {
func (v Version) toLinesNode() (node *gotree.Node) {
node = gotree.New("Version settings:")
node.Appendf("Enabled: %s", helpers.BoolPtrToYesNo(v.Enabled))
node.Appendf("Enabled: %s", gosettings.BoolToYesNo(v.Enabled))
return node
}

View File

@@ -2,10 +2,10 @@ package settings
import (
"fmt"
"strings"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/constants/vpn"
"github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/validate"
"github.com/qdm12/gotree"
)
@@ -13,19 +13,18 @@ type VPN struct {
// Type is the VPN type and can only be
// 'openvpn' or 'wireguard'. It cannot be the
// empty string in the internal state.
Type string
Provider Provider
OpenVPN OpenVPN
Wireguard Wireguard
Type string `json:"type"`
Provider Provider `json:"provider"`
OpenVPN OpenVPN `json:"openvpn"`
Wireguard Wireguard `json:"wireguard"`
}
// TODO v4 remove pointer for receiver (because of Surfshark).
func (v *VPN) Validate(storage Storage, ipv6Supported bool) (err error) {
// Validate Type
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, ", "))
if err = validate.IsOneOf(v.Type, validVPNTypes...); err != nil {
return fmt.Errorf("%w: %w", ErrVPNTypeNotValid, err)
}
err = v.Provider.validate(v.Type, storage)
@@ -58,24 +57,24 @@ func (v *VPN) Copy() (copied VPN) {
}
func (v *VPN) mergeWith(other VPN) {
v.Type = helpers.MergeWithString(v.Type, other.Type)
v.Type = gosettings.MergeWithString(v.Type, other.Type)
v.Provider.mergeWith(other.Provider)
v.OpenVPN.mergeWith(other.OpenVPN)
v.Wireguard.mergeWith(other.Wireguard)
}
func (v *VPN) OverrideWith(other VPN) {
v.Type = helpers.OverrideWithString(v.Type, other.Type)
v.Type = gosettings.OverrideWithString(v.Type, other.Type)
v.Provider.overrideWith(other.Provider)
v.OpenVPN.overrideWith(other.OpenVPN)
v.Wireguard.overrideWith(other.Wireguard)
}
func (v *VPN) setDefaults() {
v.Type = helpers.DefaultString(v.Type, vpn.OpenVPN)
v.Type = gosettings.DefaultString(v.Type, vpn.OpenVPN)
v.Provider.setDefaults()
v.OpenVPN.setDefaults(*v.Provider.Name)
v.Wireguard.setDefaults()
v.Wireguard.setDefaults(*v.Provider.Name)
}
func (v VPN) String() string {

View File

@@ -7,6 +7,8 @@ import (
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/validate"
"github.com/qdm12/gotree"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
)
@@ -15,23 +17,33 @@ import (
type Wireguard struct {
// PrivateKey is the Wireguard client peer private key.
// It cannot be nil in the internal state.
PrivateKey *string
PrivateKey *string `json:"private_key"`
// PreSharedKey is the Wireguard pre-shared key.
// It can be the empty string to indicate there
// is no pre-shared key.
// It cannot be nil in the internal state.
PreSharedKey *string
PreSharedKey *string `json:"pre_shared_key"`
// Addresses are the Wireguard interface addresses.
Addresses []netip.Prefix
Addresses []netip.Prefix `json:"addresses"`
// AllowedIPs are the Wireguard allowed IPs.
// If left unset, they default to "0.0.0.0/0"
// and, if IPv6 is supported, "::0".
AllowedIPs []netip.Prefix `json:"allowed_ips"`
// Interface is the name of the Wireguard interface
// to create. It cannot be the empty string in the
// internal state.
Interface string
Interface string `json:"interface"`
// Maximum Transmission Unit (MTU) of the Wireguard interface.
// It cannot be zero in the internal state, and defaults to
// 1400. Note it is not the wireguard-go MTU default of 1420
// because this impacts bandwidth a lot on some VPN providers,
// see https://github.com/qdm12/gluetun/issues/1650.
MTU uint16 `json:"mtu"`
// Implementation is the Wireguard implementation to use.
// It can be "auto", "userspace" or "kernelspace".
// It defaults to "auto" and cannot be the empty string
// in the internal state.
Implementation string
Implementation string `json:"implementation"`
}
var regexpInterfaceName = regexp.MustCompile(`^[a-zA-Z0-9_]+$`)
@@ -40,9 +52,11 @@ 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, ipv6Supported bool) (err error) {
if !helpers.IsOneOf(vpnProvider,
providers.Airvpn,
providers.Custom,
providers.Ivpn,
providers.Mullvad,
providers.Nordvpn,
providers.Surfshark,
providers.Windscribe,
) {
@@ -56,7 +70,12 @@ func (w Wireguard) validate(vpnProvider string, ipv6Supported bool) (err error)
}
_, err = wgtypes.ParseKey(*w.PrivateKey)
if err != nil {
return fmt.Errorf("private key is not valid: %w", err)
err = fmt.Errorf("private key is not valid: %w", err)
if vpnProvider == providers.Nordvpn &&
err.Error() == "wgtypes: incorrect key size: 48" {
err = fmt.Errorf("%w - you might be using your access token instead of the Wireguard private key", err)
}
return err
}
if vpnProvider == providers.Airvpn {
@@ -79,13 +98,26 @@ func (w Wireguard) validate(vpnProvider string, ipv6Supported bool) (err error)
}
for i, ipNet := range w.Addresses {
if !ipNet.IsValid() {
return fmt.Errorf("%w: for address at index %d: %s",
ErrWireguardInterfaceAddressNotSet, i, ipNet.String())
return fmt.Errorf("%w: for address at index %d",
ErrWireguardInterfaceAddressNotSet, i)
}
if !ipv6Supported && ipNet.Addr().Is6() {
return fmt.Errorf("%w: address %s",
ErrWireguardInterfaceAddressIPv6, ipNet)
ErrWireguardInterfaceAddressIPv6, ipNet.String())
}
}
// Validate AllowedIPs
// WARNING: do not check for IPv6 networks in the allowed IPs,
// the wireguard code will take care to ignore it.
if len(w.AllowedIPs) == 0 {
return fmt.Errorf("%w", ErrWireguardAllowedIPsNotSet)
}
for i, allowedIP := range w.AllowedIPs {
if !allowedIP.IsValid() {
return fmt.Errorf("%w: for allowed ip %d of %d",
ErrWireguardAllowedIPNotSet, i+1, len(w.AllowedIPs))
}
}
@@ -96,9 +128,8 @@ func (w Wireguard) validate(vpnProvider string, ipv6Supported bool) (err error)
}
validImplementations := []string{"auto", "userspace", "kernelspace"}
if !helpers.IsOneOf(w.Implementation, validImplementations...) {
return fmt.Errorf("%w: %s must be one of %s", ErrWireguardImplementationNotValid,
w.Implementation, helpers.ChoicesOrString(validImplementations))
if err := validate.IsOneOf(w.Implementation, validImplementations...); err != nil {
return fmt.Errorf("%w: %w", ErrWireguardImplementationNotValid, err)
}
return nil
@@ -106,35 +137,53 @@ func (w Wireguard) validate(vpnProvider string, ipv6Supported bool) (err error)
func (w *Wireguard) copy() (copied Wireguard) {
return Wireguard{
PrivateKey: helpers.CopyPointer(w.PrivateKey),
PreSharedKey: helpers.CopyPointer(w.PreSharedKey),
Addresses: helpers.CopySlice(w.Addresses),
PrivateKey: gosettings.CopyPointer(w.PrivateKey),
PreSharedKey: gosettings.CopyPointer(w.PreSharedKey),
Addresses: gosettings.CopySlice(w.Addresses),
AllowedIPs: gosettings.CopySlice(w.AllowedIPs),
Interface: w.Interface,
MTU: w.MTU,
Implementation: w.Implementation,
}
}
func (w *Wireguard) mergeWith(other Wireguard) {
w.PrivateKey = helpers.MergeWithPointer(w.PrivateKey, other.PrivateKey)
w.PreSharedKey = helpers.MergeWithPointer(w.PreSharedKey, other.PreSharedKey)
w.Addresses = helpers.MergeSlices(w.Addresses, other.Addresses)
w.Interface = helpers.MergeWithString(w.Interface, other.Interface)
w.Implementation = helpers.MergeWithString(w.Implementation, other.Implementation)
w.PrivateKey = gosettings.MergeWithPointer(w.PrivateKey, other.PrivateKey)
w.PreSharedKey = gosettings.MergeWithPointer(w.PreSharedKey, other.PreSharedKey)
w.Addresses = gosettings.MergeWithSlice(w.Addresses, other.Addresses)
w.AllowedIPs = gosettings.MergeWithSlice(w.AllowedIPs, other.AllowedIPs)
w.Interface = gosettings.MergeWithString(w.Interface, other.Interface)
w.MTU = gosettings.MergeWithNumber(w.MTU, other.MTU)
w.Implementation = gosettings.MergeWithString(w.Implementation, other.Implementation)
}
func (w *Wireguard) overrideWith(other Wireguard) {
w.PrivateKey = helpers.OverrideWithPointer(w.PrivateKey, other.PrivateKey)
w.PreSharedKey = helpers.OverrideWithPointer(w.PreSharedKey, other.PreSharedKey)
w.Addresses = helpers.OverrideWithSlice(w.Addresses, other.Addresses)
w.Interface = helpers.OverrideWithString(w.Interface, other.Interface)
w.Implementation = helpers.OverrideWithString(w.Implementation, other.Implementation)
w.PrivateKey = gosettings.OverrideWithPointer(w.PrivateKey, other.PrivateKey)
w.PreSharedKey = gosettings.OverrideWithPointer(w.PreSharedKey, other.PreSharedKey)
w.Addresses = gosettings.OverrideWithSlice(w.Addresses, other.Addresses)
w.AllowedIPs = gosettings.OverrideWithSlice(w.AllowedIPs, other.AllowedIPs)
w.Interface = gosettings.OverrideWithString(w.Interface, other.Interface)
w.MTU = gosettings.OverrideWithNumber(w.MTU, other.MTU)
w.Implementation = gosettings.OverrideWithString(w.Implementation, other.Implementation)
}
func (w *Wireguard) setDefaults() {
w.PrivateKey = helpers.DefaultPointer(w.PrivateKey, "")
w.PreSharedKey = helpers.DefaultPointer(w.PreSharedKey, "")
w.Interface = helpers.DefaultString(w.Interface, "wg0")
w.Implementation = helpers.DefaultString(w.Implementation, "auto")
func (w *Wireguard) setDefaults(vpnProvider string) {
w.PrivateKey = gosettings.DefaultPointer(w.PrivateKey, "")
w.PreSharedKey = gosettings.DefaultPointer(w.PreSharedKey, "")
if vpnProvider == providers.Nordvpn {
defaultNordVPNAddress := netip.AddrFrom4([4]byte{10, 5, 0, 2})
defaultNordVPNPrefix := netip.PrefixFrom(defaultNordVPNAddress, defaultNordVPNAddress.BitLen())
w.Addresses = gosettings.DefaultSlice(w.Addresses, []netip.Prefix{defaultNordVPNPrefix})
}
defaultAllowedIPs := []netip.Prefix{
netip.PrefixFrom(netip.IPv4Unspecified(), 0),
netip.PrefixFrom(netip.IPv6Unspecified(), 0),
}
w.AllowedIPs = gosettings.DefaultSlice(w.AllowedIPs, defaultAllowedIPs)
w.Interface = gosettings.DefaultString(w.Interface, "wg0")
const defaultMTU = 1400
w.MTU = gosettings.DefaultNumber(w.MTU, defaultMTU)
w.Implementation = gosettings.DefaultString(w.Implementation, "auto")
}
func (w Wireguard) String() string {
@@ -145,12 +194,12 @@ func (w Wireguard) toLinesNode() (node *gotree.Node) {
node = gotree.New("Wireguard settings:")
if *w.PrivateKey != "" {
s := helpers.ObfuscateWireguardKey(*w.PrivateKey)
s := gosettings.ObfuscateKey(*w.PrivateKey)
node.Appendf("Private key: %s", s)
}
if *w.PreSharedKey != "" {
s := helpers.ObfuscateWireguardKey(*w.PreSharedKey)
s := gosettings.ObfuscateKey(*w.PreSharedKey)
node.Appendf("Pre-shared key: %s", s)
}
@@ -159,7 +208,13 @@ func (w Wireguard) toLinesNode() (node *gotree.Node) {
addressesNode.Appendf(address.String())
}
node.Appendf("Network interface: %s", w.Interface)
allowedIPsNode := node.Appendf("Allowed IPs:")
for _, allowedIP := range w.AllowedIPs {
allowedIPsNode.Appendf(allowedIP.String())
}
interfaceNode := node.Appendf("Network interface: %s", w.Interface)
interfaceNode.Appendf("MTU: %d", w.MTU)
if w.Implementation != "auto" {
node.Appendf("Implementation: %s", w.Implementation)

View File

@@ -4,8 +4,9 @@ import (
"fmt"
"net/netip"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/validate"
"github.com/qdm12/gotree"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
)
@@ -15,20 +16,20 @@ type WireguardSelection struct {
// It is only used with VPN providers generating Wireguard
// configurations specific to each server and user.
// To indicate it should not be used, it should be set
// to netaddr.IPv4Unspecified(). It can never be the zero value
// to netip.IPv4Unspecified(). It can never be the zero value
// in the internal state.
EndpointIP netip.Addr
EndpointIP netip.Addr `json:"endpoint_ip"`
// EndpointPort is a the server port to use for the VPN server.
// It is optional for VPN providers IVPN, Mullvad, Surfshark
// and Windscribe, and compulsory for the others.
// When optional, it can be set to 0 to indicate not use
// a custom endpoint port. It cannot be nil in the internal
// state.
EndpointPort *uint16
EndpointPort *uint16 `json:"endpoint_port"`
// PublicKey is the server public key.
// It is only used with VPN providers generating Wireguard
// configurations specific to each server and user.
PublicKey string
PublicKey string `json:"public_key"`
}
// Validate validates WireguardSelection settings.
@@ -37,7 +38,7 @@ func (w WireguardSelection) validate(vpnProvider string) (err error) {
// Validate EndpointIP
switch vpnProvider {
case providers.Airvpn, providers.Ivpn, providers.Mullvad,
providers.Surfshark, providers.Windscribe:
providers.Nordvpn, providers.Surfshark, providers.Windscribe:
// endpoint IP addresses are baked in
case providers.Custom:
if !w.EndpointIP.IsValid() || w.EndpointIP.IsUnspecified() {
@@ -54,7 +55,7 @@ func (w WireguardSelection) validate(vpnProvider string) (err error) {
return fmt.Errorf("%w", ErrWireguardEndpointPortNotSet)
}
// EndpointPort cannot be set
case providers.Surfshark:
case providers.Surfshark, providers.Nordvpn:
if *w.EndpointPort != 0 {
return fmt.Errorf("%w", ErrWireguardEndpointPortSet)
}
@@ -76,12 +77,12 @@ func (w WireguardSelection) validate(vpnProvider string) (err error) {
allowed = []uint16{53, 80, 123, 443, 1194, 65142}
}
if helpers.Uint16IsOneOf(*w.EndpointPort, allowed) {
err = validate.IsOneOf(*w.EndpointPort, allowed...)
if err == nil {
break
}
return fmt.Errorf("%w: %d for VPN service provider %s; %s",
ErrWireguardEndpointPortNotAllowed, w.EndpointPort, vpnProvider,
helpers.PortChoicesOrString(allowed))
return fmt.Errorf("%w: for VPN service provider %s: %w",
ErrWireguardEndpointPortNotAllowed, vpnProvider, err)
default: // Providers not supporting Wireguard
}
@@ -110,26 +111,26 @@ func (w WireguardSelection) validate(vpnProvider string) (err error) {
func (w *WireguardSelection) copy() (copied WireguardSelection) {
return WireguardSelection{
EndpointIP: w.EndpointIP,
EndpointPort: helpers.CopyPointer(w.EndpointPort),
EndpointPort: gosettings.CopyPointer(w.EndpointPort),
PublicKey: w.PublicKey,
}
}
func (w *WireguardSelection) mergeWith(other WireguardSelection) {
w.EndpointIP = helpers.MergeWithIP(w.EndpointIP, other.EndpointIP)
w.EndpointPort = helpers.MergeWithPointer(w.EndpointPort, other.EndpointPort)
w.PublicKey = helpers.MergeWithString(w.PublicKey, other.PublicKey)
w.EndpointIP = gosettings.MergeWithValidator(w.EndpointIP, other.EndpointIP)
w.EndpointPort = gosettings.MergeWithPointer(w.EndpointPort, other.EndpointPort)
w.PublicKey = gosettings.MergeWithString(w.PublicKey, other.PublicKey)
}
func (w *WireguardSelection) overrideWith(other WireguardSelection) {
w.EndpointIP = helpers.OverrideWithIP(w.EndpointIP, other.EndpointIP)
w.EndpointPort = helpers.OverrideWithPointer(w.EndpointPort, other.EndpointPort)
w.PublicKey = helpers.OverrideWithString(w.PublicKey, other.PublicKey)
w.EndpointIP = gosettings.OverrideWithValidator(w.EndpointIP, other.EndpointIP)
w.EndpointPort = gosettings.OverrideWithPointer(w.EndpointPort, other.EndpointPort)
w.PublicKey = gosettings.OverrideWithString(w.PublicKey, other.PublicKey)
}
func (w *WireguardSelection) setDefaults() {
w.EndpointIP = helpers.DefaultIP(w.EndpointIP, netip.IPv4Unspecified())
w.EndpointPort = helpers.DefaultPointer(w.EndpointPort, 0)
w.EndpointIP = gosettings.DefaultValidator(w.EndpointIP, netip.IPv4Unspecified())
w.EndpointPort = gosettings.DefaultPointer(w.EndpointPort, 0)
}
func (w WireguardSelection) String() string {

View File

@@ -13,9 +13,9 @@ func (s *Source) readDNS() (dns settings.DNS, err error) {
return dns, err
}
dns.KeepNameserver, err = envToBoolPtr("DNS_KEEP_NAMESERVER")
dns.KeepNameserver, err = s.env.BoolPtr("DNS_KEEP_NAMESERVER")
if err != nil {
return dns, fmt.Errorf("environment variable DNS_KEEP_NAMESERVER: %w", err)
return dns, err
}
dns.DoT, err = s.readDoT()
@@ -27,19 +27,24 @@ func (s *Source) readDNS() (dns settings.DNS, err error) {
}
func (s *Source) readDNSServerAddress() (address netip.Addr, err error) {
key, value := s.getEnvWithRetro("DNS_ADDRESS", "DNS_PLAINTEXT_ADDRESS")
if value == "" {
const currentKey = "DNS_ADDRESS"
key := firstKeySet(s.env, "DNS_PLAINTEXT_ADDRESS", currentKey)
switch key {
case "":
return address, nil
case currentKey:
default: // Retro-compatibility
s.handleDeprecatedKey(key, currentKey)
}
address, err = netip.ParseAddr(value)
address, err = s.env.NetipAddr(key)
if err != nil {
return address, fmt.Errorf("environment variable %s: %w", key, err)
return address, err
}
// TODO remove in v4
if address.Unmap().Compare(netip.AddrFrom4([4]byte{127, 0, 0, 1})) != 0 {
s.warner.Warn(key + " is set to " + value +
s.warner.Warn(key + " is set to " + address.String() +
" so the DNS over TLS (DoT) server will not be used." +
" The default value changed to 127.0.0.1 so it uses the internal DoT serves." +
" If the DoT server fails to start, the IPv4 address of the first plaintext DNS server" +

View File

@@ -6,58 +6,44 @@ import (
"net/netip"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/govalid/binary"
"github.com/qdm12/gosettings/sources/env"
)
func (s *Source) readDNSBlacklist() (blacklist settings.DNSBlacklist, err error) {
blacklist.BlockMalicious, err = envToBoolPtr("BLOCK_MALICIOUS")
if err != nil {
return blacklist, fmt.Errorf("environment variable BLOCK_MALICIOUS: %w", err)
}
blacklist.BlockSurveillance, err = s.readBlockSurveillance()
blacklist.BlockMalicious, err = s.env.BoolPtr("BLOCK_MALICIOUS")
if err != nil {
return blacklist, err
}
blacklist.BlockAds, err = envToBoolPtr("BLOCK_ADS")
blacklist.BlockSurveillance, err = s.env.BoolPtr("BLOCK_SURVEILLANCE",
env.RetroKeys("BLOCK_NSA"))
if err != nil {
return blacklist, fmt.Errorf("environment variable BLOCK_ADS: %w", err)
return blacklist, err
}
blacklist.BlockAds, err = s.env.BoolPtr("BLOCK_ADS")
if err != nil {
return blacklist, err
}
blacklist.AddBlockedIPs, blacklist.AddBlockedIPPrefixes,
err = readDoTPrivateAddresses() // TODO v4 split in 2
err = s.readDoTPrivateAddresses() // TODO v4 split in 2
if err != nil {
return blacklist, err
}
blacklist.AllowedHosts = envToCSV("UNBLOCK") // TODO v4 change name
blacklist.AllowedHosts = s.env.CSV("UNBLOCK") // TODO v4 change name
return blacklist, nil
}
func (s *Source) readBlockSurveillance() (blocked *bool, err error) {
key, value := s.getEnvWithRetro("BLOCK_SURVEILLANCE", "BLOCK_NSA")
if value == "" {
return nil, nil //nolint:nilnil
}
blocked = new(bool)
*blocked, err = binary.Validate(value)
if err != nil {
return nil, fmt.Errorf("environment variable %s: %w", key, err)
}
return blocked, nil
}
var (
ErrPrivateAddressNotValid = errors.New("private address is not a valid IP or CIDR range")
)
func readDoTPrivateAddresses() (ips []netip.Addr,
func (s *Source) readDoTPrivateAddresses() (ips []netip.Addr,
ipPrefixes []netip.Prefix, err error) {
privateAddresses := envToCSV("DOT_PRIVATE_ADDRESS")
privateAddresses := s.env.CSV("DOT_PRIVATE_ADDRESS")
if len(privateAddresses) == 0 {
return nil, nil, nil
}

View File

@@ -1,23 +1,21 @@
package env
import (
"fmt"
"github.com/qdm12/gluetun/internal/configuration/settings"
)
func (s *Source) readDoT() (dot settings.DoT, err error) {
dot.Enabled, err = envToBoolPtr("DOT")
dot.Enabled, err = s.env.BoolPtr("DOT")
if err != nil {
return dot, fmt.Errorf("environment variable DOT: %w", err)
return dot, err
}
dot.UpdatePeriod, err = envToDurationPtr("DNS_UPDATE_PERIOD")
dot.UpdatePeriod, err = s.env.DurationPtr("DNS_UPDATE_PERIOD")
if err != nil {
return dot, fmt.Errorf("environment variable DNS_UPDATE_PERIOD: %w", err)
return dot, err
}
dot.Unbound, err = readUnbound()
dot.Unbound, err = s.readUnbound()
if err != nil {
return dot, err
}

View File

@@ -1,80 +1,36 @@
package env
import (
"errors"
"fmt"
"net/netip"
"strconv"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gosettings/sources/env"
)
func (s *Source) readFirewall() (firewall settings.Firewall, err error) {
vpnInputPortStrings := envToCSV("FIREWALL_VPN_INPUT_PORTS")
firewall.VPNInputPorts, err = stringsToPorts(vpnInputPortStrings)
firewall.VPNInputPorts, err = s.env.CSVUint16("FIREWALL_VPN_INPUT_PORTS")
if err != nil {
return firewall, fmt.Errorf("environment variable FIREWALL_VPN_INPUT_PORTS: %w", err)
return firewall, err
}
inputPortStrings := envToCSV("FIREWALL_INPUT_PORTS")
firewall.InputPorts, err = stringsToPorts(inputPortStrings)
firewall.InputPorts, err = s.env.CSVUint16("FIREWALL_INPUT_PORTS")
if err != nil {
return firewall, fmt.Errorf("environment variable FIREWALL_INPUT_PORTS: %w", err)
return firewall, err
}
outboundSubnetsKey, _ := s.getEnvWithRetro("FIREWALL_OUTBOUND_SUBNETS", "EXTRA_SUBNETS")
outboundSubnetStrings := envToCSV(outboundSubnetsKey)
firewall.OutboundSubnets, err = stringsToNetipPrefixes(outboundSubnetStrings)
firewall.OutboundSubnets, err = s.env.CSVNetipPrefixes("FIREWALL_OUTBOUND_SUBNETS",
env.RetroKeys("EXTRA_SUBNETS"))
if err != nil {
return firewall, fmt.Errorf("environment variable %s: %w", outboundSubnetsKey, err)
return firewall, err
}
firewall.Enabled, err = envToBoolPtr("FIREWALL")
firewall.Enabled, err = s.env.BoolPtr("FIREWALL")
if err != nil {
return firewall, fmt.Errorf("environment variable FIREWALL: %w", err)
return firewall, err
}
firewall.Debug, err = envToBoolPtr("FIREWALL_DEBUG")
firewall.Debug, err = s.env.BoolPtr("FIREWALL_DEBUG")
if err != nil {
return firewall, fmt.Errorf("environment variable FIREWALL_DEBUG: %w", err)
return firewall, err
}
return firewall, nil
}
var (
ErrPortParsing = errors.New("cannot parse port")
ErrPortValue = errors.New("port value is not valid")
)
func stringsToPorts(ss []string) (ports []uint16, err error) {
if len(ss) == 0 {
return nil, nil
}
ports = make([]uint16, len(ss))
for i, s := range ss {
port, err := strconv.Atoi(s)
if err != nil {
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)
}
ports[i] = uint16(port)
}
return ports, nil
}
func stringsToNetipPrefixes(ss []string) (ipPrefixes []netip.Prefix, err error) {
if len(ss) == 0 {
return nil, nil
}
ipPrefixes = make([]netip.Prefix, len(ss))
for i, s := range ss {
ipPrefixes[i], err = netip.ParsePrefix(s)
if err != nil {
return nil, fmt.Errorf("parsing IP network %q: %w", s, err)
}
}
return ipPrefixes, nil
}

View File

@@ -1,51 +1,35 @@
package env
import (
"fmt"
"time"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gosettings/sources/env"
)
func (s *Source) ReadHealth() (health settings.Health, err error) {
health.ServerAddress = getCleanedEnv("HEALTH_SERVER_ADDRESS")
_, health.TargetAddress = s.getEnvWithRetro("HEALTH_TARGET_ADDRESS", "HEALTH_ADDRESS_TO_PING")
health.ServerAddress = s.env.String("HEALTH_SERVER_ADDRESS")
health.TargetAddress = s.env.String("HEALTH_TARGET_ADDRESS",
env.RetroKeys("HEALTH_ADDRESS_TO_PING"))
successWaitPtr, err := envToDurationPtr("HEALTH_SUCCESS_WAIT_DURATION")
successWaitPtr, err := s.env.DurationPtr("HEALTH_SUCCESS_WAIT_DURATION")
if err != nil {
return health, fmt.Errorf("environment variable HEALTH_SUCCESS_WAIT_DURATION: %w", err)
return health, err
} else if successWaitPtr != nil {
health.SuccessWait = *successWaitPtr
}
health.VPN.Initial, err = s.readDurationWithRetro(
health.VPN.Initial, err = s.env.DurationPtr(
"HEALTH_VPN_DURATION_INITIAL",
"HEALTH_OPENVPN_DURATION_INITIAL")
env.RetroKeys("HEALTH_OPENVPN_DURATION_INITIAL"))
if err != nil {
return health, err
}
health.VPN.Addition, err = s.readDurationWithRetro(
health.VPN.Addition, err = s.env.DurationPtr(
"HEALTH_VPN_DURATION_ADDITION",
"HEALTH_OPENVPN_DURATION_ADDITION")
env.RetroKeys("HEALTH_OPENVPN_DURATION_ADDITION"))
if err != nil {
return health, err
}
return health, nil
}
func (s *Source) readDurationWithRetro(envKey, retroEnvKey string) (d *time.Duration, err error) {
envKey, value := s.getEnvWithRetro(envKey, retroEnvKey)
if value == "" {
return nil, nil //nolint:nilnil
}
d = new(time.Duration)
*d, err = time.ParseDuration(value)
if err != nil {
return nil, fmt.Errorf("environment variable %s: %w", envKey, err)
}
return d, nil
}

View File

@@ -3,127 +3,10 @@ package env
import (
"fmt"
"os"
"strconv"
"strings"
"time"
"github.com/qdm12/govalid/binary"
"github.com/qdm12/govalid/integer"
"github.com/qdm12/gosettings/sources/env"
)
// 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 := getCleanedEnv(envKey)
if csv == "" {
return nil
}
return lowerAndSplit(csv)
}
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 := getCleanedEnv(envKey)
if s == "" {
return nil
}
return &s
}
func envToBoolPtr(envKey string) (boolPtr *bool, err error) {
s := getCleanedEnv(envKey)
if s == "" {
return nil, nil //nolint:nilnil
}
value, err := binary.Validate(s)
if err != nil {
return nil, err
}
return &value, nil
}
func envToIntPtr(envKey string) (intPtr *int, err error) {
s := getCleanedEnv(envKey)
if s == "" {
return nil, nil //nolint:nilnil
}
value, err := strconv.Atoi(s)
if err != nil {
return nil, err
}
return &value, nil
}
func envToUint8Ptr(envKey string) (uint8Ptr *uint8, err error) {
s := getCleanedEnv(envKey)
if s == "" {
return nil, nil //nolint:nilnil
}
const min, max = 0, 255
value, err := integer.Validate(s, integer.OptionRange(min, max))
if err != nil {
return nil, err
}
uint8Ptr = new(uint8)
*uint8Ptr = uint8(value)
return uint8Ptr, nil
}
func envToUint16Ptr(envKey string) (uint16Ptr *uint16, err error) {
s := getCleanedEnv(envKey)
if s == "" {
return nil, nil //nolint:nilnil
}
const min, max = 0, 65535
value, err := integer.Validate(s, integer.OptionRange(min, max))
if err != nil {
return nil, err
}
uint16Ptr = new(uint16)
*uint16Ptr = uint16(value)
return uint16Ptr, nil
}
func envToDurationPtr(envKey string) (durationPtr *time.Duration, err error) {
s := getCleanedEnv(envKey)
if s == "" {
return nil, nil //nolint:nilnil
}
durationPtr = new(time.Duration)
*durationPtr, err = time.ParseDuration(s)
if err != nil {
return nil, err
}
return durationPtr, nil
}
func lowerAndSplit(csv string) (values []string) {
csv = strings.ToLower(csv)
return strings.Split(csv, ",")
}
func unsetEnvKeys(envKeys []string, err error) (newErr error) {
newErr = err
for _, envKey := range envKeys {
@@ -135,6 +18,16 @@ func unsetEnvKeys(envKeys []string, err error) (newErr error) {
return newErr
}
func stringPtr(s string) *string { return &s }
func uint32Ptr(n uint32) *uint32 { return &n }
func boolPtr(b bool) *bool { return &b }
func ptrTo[T any](value T) *T {
return &value
}
func firstKeySet(e env.Env, keys ...string) (firstKeySet string) {
for _, key := range keys {
value := e.Get(key)
if value != nil {
return key
}
}
return ""
}

View File

@@ -1,22 +0,0 @@
package env
import (
"os"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// setTestEnv is used to set environment variables in
// parallel tests.
func setTestEnv(t *testing.T, key, value string) {
t.Helper()
existing := os.Getenv(key)
err := os.Setenv(key, value) //nolint:tenv
t.Cleanup(func() {
err = os.Setenv(key, existing)
assert.NoError(t, err)
})
require.NoError(t, err)
}

View File

@@ -4,22 +4,32 @@ import (
"fmt"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gosettings/sources/env"
"github.com/qdm12/govalid/binary"
)
func (s *Source) readHTTPProxy() (httpProxy settings.HTTPProxy, err error) {
httpProxy.User = s.readHTTProxyUser()
httpProxy.Password = s.readHTTProxyPassword()
httpProxy.ListeningAddress = s.readHTTProxyListeningAddress()
httpProxy.User = s.env.Get("HTTPPROXY_USER",
env.RetroKeys("PROXY_USER", "TINYPROXY_USER"),
env.ForceLowercase(false))
httpProxy.Enabled, err = s.readHTTProxyEnabled()
httpProxy.Password = s.env.Get("HTTPPROXY_PASSWORD",
env.RetroKeys("PROXY_PASSWORD", "TINYPROXY_PASSWORD"),
env.ForceLowercase(false))
httpProxy.ListeningAddress, err = s.readHTTProxyListeningAddress()
if err != nil {
return httpProxy, err
}
httpProxy.Stealth, err = envToBoolPtr("HTTPPROXY_STEALTH")
httpProxy.Enabled, err = s.env.BoolPtr("HTTPPROXY", env.RetroKeys("PROXY", "TINYPROXY"))
if err != nil {
return httpProxy, fmt.Errorf("environment variable HTTPPROXY_STEALTH: %w", err)
return httpProxy, err
}
httpProxy.Stealth, err = s.env.BoolPtr("HTTPPROXY_STEALTH")
if err != nil {
return httpProxy, err
}
httpProxy.Log, err = s.readHTTProxyLog()
@@ -30,59 +40,42 @@ func (s *Source) readHTTPProxy() (httpProxy settings.HTTPProxy, err error) {
return httpProxy, nil
}
func (s *Source) readHTTProxyUser() (user *string) {
_, value := s.getEnvWithRetro("HTTPPROXY_USER", "PROXY_USER", "TINYPROXY_USER")
if value != "" {
return &value
}
return nil
}
func (s *Source) readHTTProxyPassword() (user *string) {
_, value := s.getEnvWithRetro("HTTPPROXY_PASSWORD", "PROXY_PASSWORD", "TINYPROXY_PASSWORD")
if value != "" {
return &value
}
return nil
}
func (s *Source) readHTTProxyListeningAddress() (listeningAddress string) {
key, value := s.getEnvWithRetro("HTTPPROXY_LISTENING_ADDRESS", "PROXY_PORT", "TINYPROXY_PORT", "HTTPPROXY_PORT")
if key == "HTTPPROXY_LISTENING_ADDRESS" {
return value
}
return ":" + value
}
func (s *Source) readHTTProxyEnabled() (enabled *bool, err error) {
key, value := s.getEnvWithRetro("HTTPPROXY", "PROXY", "TINYPROXY")
if value == "" {
return nil, nil //nolint:nilnil
func (s *Source) readHTTProxyListeningAddress() (listeningAddress string, err error) {
const currentKey = "HTTPPROXY_LISTENING_ADDRESS"
key := firstKeySet(s.env, "HTTPPROXY_PORT", "TINYPROXY_PORT", "PROXY_PORT",
currentKey)
switch key {
case "":
return "", nil
case currentKey:
return s.env.String(key), nil
}
enabled = new(bool)
*enabled, err = binary.Validate(value)
// Retro-compatible keys using a port only
s.handleDeprecatedKey(key, currentKey)
port, err := s.env.Uint16Ptr(key)
if err != nil {
return nil, fmt.Errorf("environment variable %s: %w", key, err)
return "", err
}
return enabled, nil
return fmt.Sprintf(":%d", *port), nil
}
func (s *Source) readHTTProxyLog() (enabled *bool, err error) {
key, value := s.getEnvWithRetro("HTTPPROXY_LOG", "PROXY_LOG_LEVEL", "TINYPROXY_LOG")
if value == "" {
const currentKey = "HTTPPROXY_LOG"
key := firstKeySet(s.env, "PROXY_LOG", "TINYPROXY_LOG", "HTTPPROXY_LOG")
switch key {
case "":
return nil, nil //nolint:nilnil
case currentKey:
return s.env.BoolPtr(key)
}
var binaryOptions []binary.Option
if key != "HTTPROXY_LOG" {
retroOption := binary.OptionEnabled("on", "info", "connect", "notice")
binaryOptions = append(binaryOptions, retroOption)
}
// Retro-compatible keys using different boolean verbs
s.handleDeprecatedKey(key, currentKey)
value := s.env.String(key)
retroOption := binary.OptionEnabled("on", "info", "connect", "notice")
enabled = new(bool)
*enabled, err = binary.Validate(value, binaryOptions...)
enabled, err = binary.Validate(value, retroOption)
if err != nil {
return nil, fmt.Errorf("environment variable %s: %w", key, err)
}

View File

@@ -9,8 +9,8 @@ import (
"github.com/qdm12/log"
)
func readLog() (log settings.Log, err error) {
log.Level, err = readLogLevel()
func (s *Source) readLog() (log settings.Log, err error) {
log.Level, err = s.readLogLevel()
if err != nil {
return log, err
}
@@ -18,14 +18,14 @@ func readLog() (log settings.Log, err error) {
return log, nil
}
func readLogLevel() (level *log.Level, err error) {
s := getCleanedEnv("LOG_LEVEL")
if s == "" {
func (s *Source) readLogLevel() (level *log.Level, err error) {
value := s.env.String("LOG_LEVEL")
if value == "" {
return nil, nil //nolint:nilnil
}
level = new(log.Level)
*level, err = parseLogLevel(s)
*level, err = parseLogLevel(value)
if err != nil {
return nil, fmt.Errorf("environment variable LOG_LEVEL: %w", err)
}

View File

@@ -1,11 +1,10 @@
package env
import (
"fmt"
"strings"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/govalid/binary"
"github.com/qdm12/gosettings/sources/env"
)
func (s *Source) readOpenVPN() (
@@ -15,113 +14,64 @@ func (s *Source) readOpenVPN() (
"OPENVPN_KEY_PASSPHRASE", "OPENVPN_ENCRYPTED_KEY"}, err)
}()
openVPN.Version = getCleanedEnv("OPENVPN_VERSION")
openVPN.User = s.readOpenVPNUser()
openVPN.Password = s.readOpenVPNPassword()
confFile := getCleanedEnv("OPENVPN_CUSTOM_CONFIG")
if confFile != "" {
openVPN.ConfFile = &confFile
}
ciphersKey, _ := s.getEnvWithRetro("OPENVPN_CIPHERS", "OPENVPN_CIPHER")
openVPN.Ciphers = envToCSV(ciphersKey)
auth := getCleanedEnv("OPENVPN_AUTH")
if auth != "" {
openVPN.Auth = &auth
}
openVPN.Cert = envToStringPtr("OPENVPN_CERT")
openVPN.Key = envToStringPtr("OPENVPN_KEY")
openVPN.EncryptedKey = envToStringPtr("OPENVPN_ENCRYPTED_KEY")
openVPN.KeyPassphrase = s.readOpenVPNKeyPassphrase()
openVPN.Version = s.env.String("OPENVPN_VERSION")
openVPN.User = s.env.Get("OPENVPN_USER",
env.RetroKeys("USER"), env.ForceLowercase(false))
openVPN.Password = s.env.Get("OPENVPN_PASSWORD",
env.RetroKeys("PASSWORD"), env.ForceLowercase(false))
openVPN.ConfFile = s.env.Get("OPENVPN_CUSTOM_CONFIG", env.ForceLowercase(false))
openVPN.Ciphers = s.env.CSV("OPENVPN_CIPHERS", env.RetroKeys("OPENVPN_CIPHER"))
openVPN.Auth = s.env.Get("OPENVPN_AUTH")
openVPN.Cert = s.env.Get("OPENVPN_CERT", env.ForceLowercase(false))
openVPN.Key = s.env.Get("OPENVPN_KEY", env.ForceLowercase(false))
openVPN.EncryptedKey = s.env.Get("OPENVPN_ENCRYPTED_KEY", env.ForceLowercase(false))
openVPN.KeyPassphrase = s.env.Get("OPENVPN_KEY_PASSPHRASE", env.ForceLowercase(false))
openVPN.PIAEncPreset = s.readPIAEncryptionPreset()
openVPN.MSSFix, err = envToUint16Ptr("OPENVPN_MSSFIX")
openVPN.MSSFix, err = s.env.Uint16Ptr("OPENVPN_MSSFIX")
if err != nil {
return openVPN, fmt.Errorf("environment variable OPENVPN_MSSFIX: %w", err)
return openVPN, err
}
_, openVPN.Interface = s.getEnvWithRetro("VPN_INTERFACE", "OPENVPN_INTERFACE")
openVPN.Interface = s.env.String("VPN_INTERFACE",
env.RetroKeys("OPENVPN_INTERFACE"), env.ForceLowercase(false))
openVPN.ProcessUser, err = s.readOpenVPNProcessUser()
if err != nil {
return openVPN, err
}
openVPN.Verbosity, err = envToIntPtr("OPENVPN_VERBOSITY")
openVPN.Verbosity, err = s.env.IntPtr("OPENVPN_VERBOSITY")
if err != nil {
return openVPN, fmt.Errorf("environment variable OPENVPN_VERBOSITY: %w", err)
return openVPN, err
}
flagsStr := getCleanedEnv("OPENVPN_FLAGS")
if flagsStr != "" {
openVPN.Flags = strings.Fields(flagsStr)
flagsPtr := s.env.Get("OPENVPN_FLAGS", env.ForceLowercase(false))
if flagsPtr != nil {
openVPN.Flags = strings.Fields(*flagsPtr)
}
return openVPN, nil
}
func (s *Source) readOpenVPNUser() (user *string) {
user = new(string)
_, *user = s.getEnvWithRetro("OPENVPN_USER", "USER")
if *user == "" {
return nil
}
// Remove spaces in user ID to simplify user's life, thanks @JeordyR
*user = strings.ReplaceAll(*user, " ", "")
return user
}
func (s *Source) readOpenVPNPassword() (password *string) {
password = new(string)
_, *password = s.getEnvWithRetro("OPENVPN_PASSWORD", "PASSWORD")
if *password == "" {
return nil
}
return password
}
func (s *Source) readOpenVPNKeyPassphrase() (passphrase *string) {
passphrase = new(string)
*passphrase = getCleanedEnv("OPENVPN_KEY_PASSPHRASE")
if *passphrase == "" {
return nil
}
return passphrase
}
func (s *Source) readPIAEncryptionPreset() (presetPtr *string) {
_, preset := s.getEnvWithRetro(
return s.env.Get(
"PRIVATE_INTERNET_ACCESS_OPENVPN_ENCRYPTION_PRESET",
"PIA_ENCRYPTION", "ENCRYPTION")
if preset != "" {
return &preset
}
return nil
env.RetroKeys("ENCRYPTION", "PIA_ENCRYPTION"))
}
func (s *Source) readOpenVPNProcessUser() (processUser string, err error) {
key, value := s.getEnvWithRetro("OPENVPN_PROCESS_USER", "OPENVPN_ROOT")
if key == "OPENVPN_PROCESS_USER" {
return value, nil
value, err := s.env.BoolPtr("OPENVPN_ROOT") // Retro-compatibility
if err != nil {
return "", err
} else if value != nil {
if *value {
return "root", nil
}
const defaultNonRootUser = "nonrootuser"
return defaultNonRootUser, nil
}
// Retro-compatibility
if value == "" {
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
return s.env.String("OPENVPN_PROCESS_USER"), nil
}

View File

@@ -7,22 +7,20 @@ import (
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/govalid/port"
"github.com/qdm12/gosettings/sources/env"
)
func (s *Source) readOpenVPNSelection() (
selection settings.OpenVPNSelection, err error) {
confFile := getCleanedEnv("OPENVPN_CUSTOM_CONFIG")
if confFile != "" {
selection.ConfFile = &confFile
}
selection.ConfFile = s.env.Get("OPENVPN_CUSTOM_CONFIG", env.ForceLowercase(false))
selection.TCP, err = s.readOpenVPNProtocol()
if err != nil {
return selection, err
}
selection.CustomPort, err = s.readOpenVPNCustomPort()
selection.CustomPort, err = s.env.Uint16Ptr("VPN_ENDPOINT_PORT",
env.RetroKeys("PORT", "OPENVPN_PORT"))
if err != nil {
return selection, err
}
@@ -35,32 +33,24 @@ func (s *Source) readOpenVPNSelection() (
var ErrOpenVPNProtocolNotValid = errors.New("OpenVPN protocol is not valid")
func (s *Source) readOpenVPNProtocol() (tcp *bool, err error) {
envKey, protocol := s.getEnvWithRetro("OPENVPN_PROTOCOL", "PROTOCOL")
switch strings.ToLower(protocol) {
const currentKey = "OPENVPN_PROTOCOL"
envKey := firstKeySet(s.env, "PROTOCOL", currentKey)
switch envKey {
case "":
return nil, nil //nolint:nilnil
case currentKey:
default: // Retro compatibility
s.handleDeprecatedKey(envKey, currentKey)
}
protocol := s.env.String(envKey)
switch strings.ToLower(protocol) {
case constants.UDP:
return boolPtr(false), nil
return ptrTo(false), nil
case constants.TCP:
return boolPtr(true), nil
return ptrTo(true), nil
default:
return nil, fmt.Errorf("environment variable %s: %w: %s",
envKey, ErrOpenVPNProtocolNotValid, protocol)
}
}
func (s *Source) readOpenVPNCustomPort() (customPort *uint16, err error) {
key, value := s.getEnvWithRetro("VPN_ENDPOINT_PORT", "PORT", "OPENVPN_PORT")
if value == "" {
return nil, nil //nolint:nilnil
}
customPort = new(uint16)
*customPort, err = port.Validate(value)
if err != nil {
return nil, fmt.Errorf("environment variable %s: %w", key, err)
}
return customPort, nil
}

View File

@@ -1,28 +1,33 @@
package env
import (
"fmt"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gosettings/sources/env"
)
func (s *Source) readPortForward() (
portForwarding settings.PortForwarding, err error) {
key, _ := s.getEnvWithRetro(
"VPN_PORT_FORWARDING",
"PRIVATE_INTERNET_ACCESS_VPN_PORT_FORWARDING",
"PORT_FORWARDING")
portForwarding.Enabled, err = envToBoolPtr(key)
portForwarding.Enabled, err = s.env.BoolPtr("VPN_PORT_FORWARDING",
env.RetroKeys(
"PORT_FORWARDING",
"PRIVATE_INTERNET_ACCESS_VPN_PORT_FORWARDING",
))
if err != nil {
return portForwarding, fmt.Errorf("environment variable %s: %w", key, err)
return portForwarding, err
}
_, value := s.getEnvWithRetro(
"VPN_PORT_FORWARDING_STATUS_FILE",
"PRIVATE_INTERNET_ACCESS_VPN_PORT_FORWARDING_STATUS_FILE",
"PORT_FORWARDING_STATUS_FILE")
if value != "" {
portForwarding.Filepath = stringPtr(value)
portForwarding.Provider = s.env.Get("VPN_PORT_FORWARDING_PROVIDER")
portForwarding.Filepath = s.env.Get("VPN_PORT_FORWARDING_STATUS_FILE",
env.ForceLowercase(false),
env.RetroKeys(
"PORT_FORWARDING_STATUS_FILE",
"PRIVATE_INTERNET_ACCESS_VPN_PORT_FORWARDING_STATUS_FILE",
))
portForwarding.ListeningPort, err = s.env.Uint16Ptr("VPN_PORT_FORWARDING_LISTENING_PORT")
if err != nil {
return portForwarding, err
}
return portForwarding, nil

View File

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

View File

@@ -7,6 +7,7 @@ import (
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/constants/vpn"
"github.com/qdm12/gosettings/sources/env"
)
func (s *Source) readProvider(vpnType string) (provider settings.Provider, err error) {
@@ -30,19 +31,20 @@ func (s *Source) readProvider(vpnType string) (provider settings.Provider, err e
}
func (s *Source) readVPNServiceProvider(vpnType string) (vpnProviderPtr *string) {
_, value := s.getEnvWithRetro("VPN_SERVICE_PROVIDER", "VPNSP")
if value == "" {
if vpnType != vpn.Wireguard && getCleanedEnv("OPENVPN_CUSTOM_CONFIG") != "" {
valuePtr := s.env.Get("VPN_SERVICE_PROVIDER", env.RetroKeys("VPNSP"))
if valuePtr == nil {
if vpnType != vpn.Wireguard && s.env.Get("OPENVPN_CUSTOM_CONFIG") != nil {
// retro compatibility
return stringPtr(providers.Custom)
return ptrTo(providers.Custom)
}
return nil
}
value := *valuePtr
value = strings.ToLower(value)
if value == "pia" { // retro compatibility
return stringPtr(providers.PrivateInternetAccess)
return ptrTo(providers.PrivateInternetAccess)
}
return stringPtr(value)
return ptrTo(value)
}

View File

@@ -1,42 +1,22 @@
package env
import (
"fmt"
"time"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gosettings/sources/env"
)
func (s *Source) readPublicIP() (publicIP settings.PublicIP, err error) {
publicIP.Period, err = readPublicIPPeriod()
publicIP.Period, err = s.env.DurationPtr("PUBLICIP_PERIOD")
if err != nil {
return publicIP, err
}
publicIP.IPFilepath = s.readPublicIPFilepath()
publicIP.IPFilepath = s.env.Get("PUBLICIP_FILE",
env.ForceLowercase(false), env.RetroKeys("IP_STATUS_FILE"))
publicIP.API = s.env.String("PUBLICIP_API")
publicIP.APIToken = s.env.Get("PUBLICIP_API_TOKEN")
return publicIP, nil
}
func readPublicIPPeriod() (period *time.Duration, err error) {
s := getCleanedEnv("PUBLICIP_PERIOD")
if s == "" {
return nil, nil //nolint:nilnil
}
period = new(time.Duration)
*period, err = time.ParseDuration(s)
if err != nil {
return nil, fmt.Errorf("environment variable PUBLICIP_PERIOD: %w", err)
}
return period, nil
}
func (s *Source) readPublicIPFilepath() (filepath *string) {
_, value := s.getEnvWithRetro("PUBLICIP_FILE", "IP_STATUS_FILE")
if value != "" {
return &value
}
return nil
}

View File

@@ -1,11 +1,16 @@
package env
import (
"os"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gosettings/sources/env"
)
type Source struct {
warner Warner
env env.Env
warner Warner
handleDeprecatedKey func(deprecatedKey, newKey string)
}
type Warner interface {
@@ -13,8 +18,16 @@ type Warner interface {
}
func New(warner Warner) *Source {
handleDeprecatedKey := func(deprecatedKey, newKey string) {
warner.Warn(
"You are using the old environment variable " + deprecatedKey +
", please consider changing it to " + newKey)
}
return &Source{
warner: warner,
env: *env.New(os.Environ(), handleDeprecatedKey),
warner: warner,
handleDeprecatedKey: handleDeprecatedKey,
}
}
@@ -46,7 +59,7 @@ func (s *Source) Read() (settings settings.Settings, err error) {
return settings, err
}
settings.Log, err = readLog()
settings.Log, err = s.readLog()
if err != nil {
return settings, err
}
@@ -56,12 +69,12 @@ func (s *Source) Read() (settings settings.Settings, err error) {
return settings, err
}
settings.Updater, err = readUpdater()
settings.Updater, err = s.readUpdater()
if err != nil {
return settings, err
}
settings.Version, err = readVersion()
settings.Version, err = s.readVersion()
if err != nil {
return settings, err
}
@@ -81,37 +94,10 @@ func (s *Source) Read() (settings settings.Settings, err error) {
return settings, err
}
settings.Pprof, err = readPprof()
settings.Pprof, err = s.readPprof()
if err != nil {
return settings, err
}
return settings, nil
}
func (s *Source) onRetroActive(oldKey, newKey string) {
s.warner.Warn(
"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 (s *Source) 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 != "" {
s.onRetroActive(key, currentKey)
return key, value
}
}
return currentKey, getCleanedEnv(currentKey)
}

View File

@@ -1,14 +1,11 @@
package env
import (
"fmt"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/govalid/binary"
)
func (s *Source) readControlServer() (controlServer settings.ControlServer, err error) {
controlServer.Log, err = readControlServerLog()
controlServer.Log, err = s.env.BoolPtr("HTTP_CONTROL_SERVER_LOG")
if err != nil {
return controlServer, err
}
@@ -18,31 +15,17 @@ func (s *Source) readControlServer() (controlServer settings.ControlServer, err
return controlServer, nil
}
func readControlServerLog() (enabled *bool, err error) {
s := getCleanedEnv("HTTP_CONTROL_SERVER_LOG")
if s == "" {
return nil, nil //nolint:nilnil
}
log, err := binary.Validate(s)
if err != nil {
return nil, fmt.Errorf("environment variable HTTP_CONTROL_SERVER_LOG: %w", err)
}
return &log, nil
}
func (s *Source) readControlServerAddress() (address *string) {
key, value := s.getEnvWithRetro("HTTP_CONTROL_SERVER_ADDRESS", "HTTP_CONTROL_SERVER_PORT")
if value == "" {
const currentKey = "HTTP_CONTROL_SERVER_ADDRESS"
key := firstKeySet(s.env, "CONTROL_SERVER_ADDRESS", currentKey)
if key == currentKey {
return s.env.Get(key)
}
s.handleDeprecatedKey(key, currentKey)
value := s.env.Get("CONTROL_SERVER_ADDRESS")
if value == nil {
return nil
}
if key == "HTTP_CONTROL_SERVER_ADDRESS" {
return &value
}
address = new(string)
*address = ":" + value
return address
return ptrTo(":" + *value)
}

View File

@@ -2,98 +2,76 @@ package env
import (
"errors"
"fmt"
"net/netip"
"strconv"
"strings"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/constants/providers"
)
var (
ErrServerNumberNotValid = errors.New("server number is not valid")
"github.com/qdm12/gosettings/sources/env"
)
func (s *Source) readServerSelection(vpnProvider, vpnType string) (
ss settings.ServerSelection, err error) {
ss.VPN = vpnType
ss.TargetIP, err = s.readOpenVPNTargetIP()
ss.TargetIP, err = s.env.NetipAddr("VPN_ENDPOINT_IP",
env.RetroKeys("OPENVPN_TARGET_IP"))
if err != nil {
return ss, err
}
countriesKey, _ := s.getEnvWithRetro("SERVER_COUNTRIES", "COUNTRY")
ss.Countries = envToCSV(countriesKey)
ss.Countries = s.env.CSV("SERVER_COUNTRIES", env.RetroKeys("COUNTRY"))
if vpnProvider == providers.Cyberghost && len(ss.Countries) == 0 {
// Retro-compatibility for Cyberghost using the REGION variable
ss.Countries = envToCSV("REGION")
ss.Countries = s.env.CSV("REGION")
if len(ss.Countries) > 0 {
s.onRetroActive("REGION", "SERVER_COUNTRIES")
s.handleDeprecatedKey("REGION", "SERVER_COUNTRIES")
}
}
regionsKey, _ := s.getEnvWithRetro("SERVER_REGIONS", "REGION")
ss.Regions = envToCSV(regionsKey)
citiesKey, _ := s.getEnvWithRetro("SERVER_CITIES", "CITY")
ss.Cities = envToCSV(citiesKey)
ss.ISPs = envToCSV("ISP")
hostnamesKey, _ := s.getEnvWithRetro("SERVER_HOSTNAMES", "SERVER_HOSTNAME")
ss.Hostnames = envToCSV(hostnamesKey)
serverNamesKey, _ := s.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 {
const base, bitSize = 10, 16
number, err := strconv.ParseInt(numberString, base, bitSize)
if err != nil {
return ss, fmt.Errorf("%w: %s",
ErrServerNumberNotValid, numberString)
} else if number < 0 || number > 65535 {
return ss, fmt.Errorf("%w: %d must be between 0 and 65535",
ErrServerNumberNotValid, number)
}
numbers[i] = uint16(number)
}
ss.Numbers = numbers
ss.Regions = s.env.CSV("SERVER_REGIONS", env.RetroKeys("REGION"))
ss.Cities = s.env.CSV("SERVER_CITIES", env.RetroKeys("CITY"))
ss.ISPs = s.env.CSV("ISP")
ss.Hostnames = s.env.CSV("SERVER_HOSTNAMES", env.RetroKeys("SERVER_HOSTNAME"))
ss.Names = s.env.CSV("SERVER_NAMES", env.RetroKeys("SERVER_NAME"))
ss.Numbers, err = s.env.CSVUint16("SERVER_NUMBER")
ss.Categories = s.env.CSV("SERVER_CATEGORIES")
if err != nil {
return ss, err
}
// Mullvad only
ss.OwnedOnly, err = s.readOwnedOnly()
ss.OwnedOnly, err = s.env.BoolPtr("OWNED_ONLY", env.RetroKeys("OWNED"))
if err != nil {
return ss, err
}
// VPNUnlimited and ProtonVPN only
ss.FreeOnly, err = envToBoolPtr("FREE_ONLY")
ss.FreeOnly, err = s.env.BoolPtr("FREE_ONLY")
if err != nil {
return ss, fmt.Errorf("environment variable FREE_ONLY: %w", err)
return ss, err
}
// VPNSecure only
ss.PremiumOnly, err = envToBoolPtr("PREMIUM_ONLY")
ss.PremiumOnly, err = s.env.BoolPtr("PREMIUM_ONLY")
if err != nil {
return ss, fmt.Errorf("environment variable PREMIUM_ONLY: %w", err)
return ss, err
}
// Surfshark only
ss.MultiHopOnly, err = s.env.BoolPtr("MULTIHOP_ONLY")
if err != nil {
return ss, err
}
// VPNUnlimited only
ss.MultiHopOnly, err = envToBoolPtr("MULTIHOP_ONLY")
ss.StreamOnly, err = s.env.BoolPtr("STREAM_ONLY")
if err != nil {
return ss, fmt.Errorf("environment variable MULTIHOP_ONLY: %w", err)
return ss, err
}
// VPNUnlimited only
ss.MultiHopOnly, err = envToBoolPtr("STREAM_ONLY")
// PIA only
ss.PortForwardOnly, err = s.env.BoolPtr("PORT_FORWARD_ONLY")
if err != nil {
return ss, fmt.Errorf("environment variable STREAM_ONLY: %w", err)
return ss, err
}
ss.OpenVPN, err = s.readOpenVPNSelection()
@@ -112,26 +90,3 @@ func (s *Source) readServerSelection(vpnProvider, vpnType string) (
var (
ErrInvalidIP = errors.New("invalid IP address")
)
func (s *Source) readOpenVPNTargetIP() (ip netip.Addr, err error) {
envKey, value := s.getEnvWithRetro("VPN_ENDPOINT_IP", "OPENVPN_TARGET_IP")
if value == "" {
return ip, nil
}
ip, err = netip.ParseAddr(value)
if err != nil {
return ip, fmt.Errorf("environment variable %s: %w", envKey, err)
}
return ip, nil
}
func (s *Source) readOwnedOnly() (ownedOnly *bool, err error) {
envKey, _ := s.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,43 +2,41 @@ package env
import (
"fmt"
"strings"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gosettings/sources/env"
)
func (s *Source) readShadowsocks() (shadowsocks settings.Shadowsocks, err error) {
shadowsocks.Enabled, err = envToBoolPtr("SHADOWSOCKS")
shadowsocks.Enabled, err = s.env.BoolPtr("SHADOWSOCKS")
if err != nil {
return shadowsocks, fmt.Errorf("environment variable SHADOWSOCKS: %w", err)
return shadowsocks, err
}
shadowsocks.Address = s.readShadowsocksAddress()
shadowsocks.LogAddresses, err = envToBoolPtr("SHADOWSOCKS_LOG")
shadowsocks.Address, err = s.readShadowsocksAddress()
if err != nil {
return shadowsocks, fmt.Errorf("environment variable SHADOWSOCKS_LOG: %w", err)
return shadowsocks, err
}
shadowsocks.CipherName = s.readShadowsocksCipher()
shadowsocks.Password = envToStringPtr("SHADOWSOCKS_PASSWORD")
shadowsocks.LogAddresses, err = s.env.BoolPtr("SHADOWSOCKS_LOG")
if err != nil {
return shadowsocks, err
}
shadowsocks.CipherName = s.env.String("SHADOWSOCKS_CIPHER",
env.RetroKeys("SHADOWSOCKS_METHOD"))
shadowsocks.Password = s.env.Get("SHADOWSOCKS_PASSWORD", env.ForceLowercase(false))
return shadowsocks, nil
}
func (s *Source) readShadowsocksAddress() (address string) {
key, value := s.getEnvWithRetro("SHADOWSOCKS_LISTENING_ADDRESS", "SHADOWSOCKS_PORT")
if value == "" {
return ""
func (s *Source) readShadowsocksAddress() (address *string, err error) {
const currentKey = "SHADOWSOCKS_LISTENING_ADDRESS"
port, err := s.env.Uint16Ptr("SHADOWSOCKS_PORT") // retro-compatibility
if err != nil {
return nil, err
} else if port != nil {
s.handleDeprecatedKey("SHADOWSOCKS_PORT", currentKey)
return ptrTo(fmt.Sprintf(":%d", *port)), nil
}
if key == "SHADOWSOCKS_LISTENING_ADDRESS" {
return value
}
// Retro-compatibility
return ":" + value
}
func (s *Source) readShadowsocksCipher() (cipher string) {
_, cipher = s.getEnvWithRetro("SHADOWSOCKS_CIPHER", "SHADOWSOCKS_METHOD")
return strings.ToLower(cipher)
return s.env.Get(currentKey), nil
}

View File

@@ -1,55 +1,22 @@
package env
import (
"errors"
"fmt"
"strconv"
"github.com/qdm12/gluetun/internal/configuration/settings"
)
var (
ErrSystemPUIDNotValid = errors.New("PUID is not valid")
ErrSystemPGIDNotValid = errors.New("PGID is not valid")
ErrSystemTimezoneNotValid = errors.New("timezone is not valid")
"github.com/qdm12/gosettings/sources/env"
)
func (s *Source) readSystem() (system settings.System, err error) {
system.PUID, err = s.readID("PUID", "UID")
system.PUID, err = s.env.Uint32Ptr("PUID", env.RetroKeys("UID"))
if err != nil {
return system, err
}
system.PGID, err = s.readID("PGID", "GID")
system.PGID, err = s.env.Uint32Ptr("PGID", env.RetroKeys("GID"))
if err != nil {
return system, err
}
system.Timezone = getCleanedEnv("TZ")
system.Timezone = s.env.String("TZ")
return system, nil
}
var ErrSystemIDNotValid = errors.New("system ID is not valid")
func (s *Source) readID(key, retroKey string) (
id *uint32, err error) {
idEnvKey, idString := s.getEnvWithRetro(key, retroKey)
if idString == "" {
return nil, nil //nolint:nilnil
}
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",
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 uint32Ptr(uint32(idUint64)), nil
}

View File

@@ -1,88 +0,0 @@
package env
import (
"testing"
"github.com/stretchr/testify/assert"
)
func Test_Reader_readID(t *testing.T) {
t.Parallel()
testCases := map[string]struct {
keyPrefix string
keyValue string
retroKeyPrefix string
retroValue string
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: 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`,
},
}
for name, testCase := range testCases {
testCase := testCase
t.Run(name, func(t *testing.T) {
t.Parallel()
suffix := t.Name()
key := testCase.keyPrefix + suffix
retroKey := testCase.retroKeyPrefix + suffix
setTestEnv(t, key, testCase.keyValue)
setTestEnv(t, retroKey, testCase.retroValue)
source := &Source{}
id, err := source.readID(key, retroKey)
assert.ErrorIs(t, err, testCase.errWrapped)
if err != nil {
assert.EqualError(t, err, testCase.errMessage)
}
assert.Equal(t, testCase.id, id)
})
}
}

View File

@@ -1,37 +1,35 @@
package env
import (
"fmt"
"github.com/qdm12/gluetun/internal/configuration/settings"
)
func readUnbound() (unbound settings.Unbound, err error) {
unbound.Providers = envToCSV("DOT_PROVIDERS")
func (s *Source) readUnbound() (unbound settings.Unbound, err error) {
unbound.Providers = s.env.CSV("DOT_PROVIDERS")
unbound.Caching, err = envToBoolPtr("DOT_CACHING")
unbound.Caching, err = s.env.BoolPtr("DOT_CACHING")
if err != nil {
return unbound, fmt.Errorf("environment variable DOT_CACHING: %w", err)
return unbound, err
}
unbound.IPv6, err = envToBoolPtr("DOT_IPV6")
unbound.IPv6, err = s.env.BoolPtr("DOT_IPV6")
if err != nil {
return unbound, fmt.Errorf("environment variable DOT_IPV6: %w", err)
return unbound, err
}
unbound.VerbosityLevel, err = envToUint8Ptr("DOT_VERBOSITY")
unbound.VerbosityLevel, err = s.env.Uint8Ptr("DOT_VERBOSITY")
if err != nil {
return unbound, fmt.Errorf("environment variable DOT_VERBOSITY: %w", err)
return unbound, err
}
unbound.VerbosityDetailsLevel, err = envToUint8Ptr("DOT_VERBOSITY_DETAILS")
unbound.VerbosityDetailsLevel, err = s.env.Uint8Ptr("DOT_VERBOSITY_DETAILS")
if err != nil {
return unbound, fmt.Errorf("environment variable DOT_VERBOSITY_DETAILS: %w", err)
return unbound, err
}
unbound.ValidationLogLevel, err = envToUint8Ptr("DOT_VALIDATION_LOGLEVEL")
unbound.ValidationLogLevel, err = s.env.Uint8Ptr("DOT_VALIDATION_LOGLEVEL")
if err != nil {
return unbound, fmt.Errorf("environment variable DOT_VALIDATION_LOGLEVEL: %w", err)
return unbound, err
}
return unbound, nil

View File

@@ -1,14 +1,11 @@
package env
import (
"fmt"
"time"
"github.com/qdm12/gluetun/internal/configuration/settings"
)
func readUpdater() (updater settings.Updater, err error) {
updater.Period, err = readUpdaterPeriod()
func (s *Source) readUpdater() (updater settings.Updater, err error) {
updater.Period, err = s.env.DurationPtr("UPDATER_PERIOD")
if err != nil {
return updater, err
}
@@ -18,29 +15,16 @@ func readUpdater() (updater settings.Updater, err error) {
return updater, err
}
updater.MinRatio, err = envToFloat64("UPDATER_MIN_RATIO")
updater.MinRatio, err = s.env.Float64("UPDATER_MIN_RATIO")
if err != nil {
return updater, fmt.Errorf("environment variable UPDATER_MIN_RATIO: %w", err)
return updater, err
}
updater.Providers = envToCSV("UPDATER_VPN_SERVICE_PROVIDERS")
updater.Providers = s.env.CSV("UPDATER_VPN_SERVICE_PROVIDERS")
return updater, nil
}
func readUpdaterPeriod() (period *time.Duration, err error) {
s := getCleanedEnv("UPDATER_PERIOD")
if s == "" {
return nil, nil //nolint:nilnil
}
period = new(time.Duration)
*period, err = time.ParseDuration(s)
if err != nil {
return nil, fmt.Errorf("environment variable UPDATER_PERIOD: %w", err)
}
return period, nil
}
func readUpdaterDNSAddress() (address string, err error) {
// TODO this is currently using Cloudflare in
// plaintext to not be blocked by DNS over TLS by default.

View File

@@ -1,32 +1,14 @@
package env
import (
"fmt"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/govalid/binary"
)
func readVersion() (version settings.Version, err error) {
version.Enabled, err = readVersionEnabled()
func (s *Source) readVersion() (version settings.Version, err error) {
version.Enabled, err = s.env.BoolPtr("VERSION_INFORMATION")
if err != nil {
return version, err
}
return version, nil
}
func readVersionEnabled() (enabled *bool, err error) {
s := getCleanedEnv("VERSION_INFORMATION")
if s == "" {
return nil, nil //nolint:nilnil
}
enabled = new(bool)
*enabled, err = binary.Validate(s)
if err != nil {
return nil, fmt.Errorf("environment variable VERSION_INFORMATION: %w", err)
}
return enabled, nil
}

View File

@@ -2,13 +2,12 @@ package env
import (
"fmt"
"strings"
"github.com/qdm12/gluetun/internal/configuration/settings"
)
func (s *Source) readVPN() (vpn settings.VPN, err error) {
vpn.Type = strings.ToLower(getCleanedEnv("VPN_TYPE"))
vpn.Type = s.env.String("VPN_TYPE")
vpn.Provider, err = s.readProvider(vpn.Type)
if err != nil {

View File

@@ -1,44 +1,33 @@
package env
import (
"fmt"
"net/netip"
"os"
"strings"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gosettings/sources/env"
)
func (s *Source) readWireguard() (wireguard settings.Wireguard, err error) {
defer func() {
err = unsetEnvKeys([]string{"WIREGUARD_PRIVATE_KEY", "WIREGUARD_PRESHARED_KEY"}, err)
}()
wireguard.PrivateKey = envToStringPtr("WIREGUARD_PRIVATE_KEY")
wireguard.PreSharedKey = envToStringPtr("WIREGUARD_PRESHARED_KEY")
_, wireguard.Interface = s.getEnvWithRetro("VPN_INTERFACE", "WIREGUARD_INTERFACE")
wireguard.Implementation = os.Getenv("WIREGUARD_IMPLEMENTATION")
wireguard.Addresses, err = s.readWireguardAddresses()
wireguard.PrivateKey = s.env.Get("WIREGUARD_PRIVATE_KEY", env.ForceLowercase(false))
wireguard.PreSharedKey = s.env.Get("WIREGUARD_PRESHARED_KEY", env.ForceLowercase(false))
wireguard.Interface = s.env.String("VPN_INTERFACE",
env.RetroKeys("WIREGUARD_INTERFACE"), env.ForceLowercase(false))
wireguard.Implementation = s.env.String("WIREGUARD_IMPLEMENTATION")
wireguard.Addresses, err = s.env.CSVNetipPrefixes("WIREGUARD_ADDRESSES",
env.RetroKeys("WIREGUARD_ADDRESS"))
if err != nil {
return wireguard, err // already wrapped
}
wireguard.AllowedIPs, err = s.env.CSVNetipPrefixes("WIREGUARD_ALLOWED_IPS")
if err != nil {
return wireguard, err // already wrapped
}
mtuPtr, err := s.env.Uint16Ptr("WIREGUARD_MTU")
if err != nil {
return wireguard, err
} else if mtuPtr != nil {
wireguard.MTU = *mtuPtr
}
return wireguard, nil
}
func (s *Source) readWireguardAddresses() (addresses []netip.Prefix, err error) {
key, addressesCSV := s.getEnvWithRetro("WIREGUARD_ADDRESSES", "WIREGUARD_ADDRESS")
if addressesCSV == "" {
return nil, nil
}
addressStrings := strings.Split(addressesCSV, ",")
addresses = make([]netip.Prefix, len(addressStrings))
for i, addressString := range addressStrings {
addressString = strings.TrimSpace(addressString)
addresses[i], err = netip.ParsePrefix(addressString)
if err != nil {
return nil, fmt.Errorf("environment variable %s: %w", key, err)
}
}
return addresses, nil
}

View File

@@ -1,55 +1,23 @@
package env
import (
"fmt"
"net/netip"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/govalid/port"
"github.com/qdm12/gosettings/sources/env"
)
func (s *Source) readWireguardSelection() (
selection settings.WireguardSelection, err error) {
selection.EndpointIP, err = s.readWireguardEndpointIP()
selection.EndpointIP, err = s.env.NetipAddr("VPN_ENDPOINT_IP", env.RetroKeys("WIREGUARD_ENDPOINT_IP"))
if err != nil {
return selection, err
}
selection.EndpointPort, err = s.readWireguardCustomPort()
selection.EndpointPort, err = s.env.Uint16Ptr("VPN_ENDPOINT_PORT", env.RetroKeys("WIREGUARD_ENDPOINT_PORT"))
if err != nil {
return selection, err
}
selection.PublicKey = getCleanedEnv("WIREGUARD_PUBLIC_KEY")
selection.PublicKey = s.env.String("WIREGUARD_PUBLIC_KEY", env.ForceLowercase(false))
return selection, nil
}
func (s *Source) readWireguardEndpointIP() (endpointIP netip.Addr, err error) {
key, value := s.getEnvWithRetro("VPN_ENDPOINT_IP", "WIREGUARD_ENDPOINT_IP")
if value == "" {
return endpointIP, nil
}
endpointIP, err = netip.ParseAddr(value)
if err != nil {
return endpointIP, fmt.Errorf("environment variable %s: %w", key, err)
}
return endpointIP, nil
}
func (s *Source) readWireguardCustomPort() (customPort *uint16, err error) {
key, value := s.getEnvWithRetro("VPN_ENDPOINT_PORT", "WIREGUARD_ENDPOINT_PORT")
if value == "" {
return nil, nil //nolint:nilnil
}
customPort = new(uint16)
*customPort, err = port.Validate(value)
if err != nil {
return nil, fmt.Errorf("environment variable %s: %w", key, err)
}
return customPort, nil
}

View File

@@ -0,0 +1,3 @@
package files
func ptrTo[T any](x T) *T { return &x }

View File

@@ -0,0 +1,16 @@
package files
import (
"fmt"
"github.com/qdm12/gluetun/internal/configuration/settings"
)
func (s *Source) readProvider() (provider settings.Provider, err error) {
provider.ServerSelection, err = s.readServerSelection()
if err != nil {
return provider, fmt.Errorf("server selection: %w", err)
}
return provider, nil
}

View File

@@ -4,10 +4,15 @@ import (
"github.com/qdm12/gluetun/internal/configuration/settings"
)
type Source struct{}
type Source struct {
wireguardConfigPath string
}
func New() *Source {
return &Source{}
const wireguardConfigPath = "/gluetun/wireguard/wg0.conf"
return &Source{
wireguardConfigPath: wireguardConfigPath,
}
}
func (s *Source) String() string { return "files" }

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