Skip to content

PureVPN provider revamp#3165

Draft
reedog117 wants to merge 22 commits into
passteque:masterfrom
reedog117:codex/purevpn-deb-updater
Draft

PureVPN provider revamp#3165
reedog117 wants to merge 22 commits into
passteque:masterfrom
reedog117:codex/purevpn-deb-updater

Conversation

@reedog117

@reedog117 reedog117 commented Feb 27, 2026

Copy link
Copy Markdown

Description

PureVPN provider revamp:

  • integrates PureVPN inventory-driven server selection and OpenVPN template ingestion by extracting this from the PureVPN Linux client
  • removes legacy fallback port behavior in OpenVPN config generation and uses inventory-provided ports
  • aligns filtering behavior around stable location fields and inventory tags (including p2p/category/type filtering updates)

Issue

Assertions

  • I am aware that we do not accept manual changes to the servers.json file
  • I am aware that any changes to settings should be reflected in the wiki

@reedog117

Copy link
Copy Markdown
Author

This PR is more comprehensive but addresses much of the shortcomings of #3149 by finding a better avenue to get an updated server list and parsing accepted protocols, port numbers, and other attributes directly from the same source used by PureVPN's GUI clients.

@reedog117

Copy link
Copy Markdown
Author

Test results summary for the PureVPN provider revamp branch.

Branch / commit

  • Branch: codex/purevpn-deb-updater
  • Head: 9c634605

1) Targeted validation on host (macOS)

go build ./cmd/gluetun
go test ./internal/provider/purevpn/... ./internal/provider/purevpn/updater/...

Result: ✅ pass

2) Targeted validation in Linux amd64 container

docker run --rm --platform linux/amd64 \
  -v "$PWD":/src -w /src golang:1.25-alpine3.22 \
  sh -lc '/usr/local/go/bin/go build ./cmd/gluetun && /usr/local/go/bin/go test ./internal/provider/purevpn/... ./internal/provider/purevpn/updater/...'

Result: ✅ pass

3) Full suite on host (informational)

go test ./...

Result: ⚠️ not fully green in this local macOS environment due unrelated/platform-sensitive tests:

  • internal/natpmp (UDP/rpc timeout expectation mismatch)
  • internal/netlink (wireguard support panic on unspecified platform)
  • internal/tun (operation not permitted on TUN node creation)

PureVPN-related packages are green in all runs above.

@reedog117

Copy link
Copy Markdown
Author

This potentially also closes #1501 #1725 and #1653

Comment thread internal/openvpn/logs.go
1. The VPN server IP address you are trying to connect to is no longer valid 🔌
Check out https://github.com/qdm12/gluetun-wiki/blob/main/setup/servers.md#update-the-vpn-servers-list

2. The VPN server crashed 💥, try changing your VPN servers filtering options such as SERVER_REGIONS

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

revert

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed. I reverted this message change and restored SERVER_REGIONS in internal/openvpn/logs.go (and corresponding test).

Comment on lines +157 to +158
_ = ss
_ = warner

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

??????

Also we want to maintain retro-compatibility

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed. I removed the placeholder _ = ss / _ = warner edits and restored the Surfshark retro-compatibility branch in getLocationFilterChoices.

Comment thread internal/configuration/settings/serverselection.go Outdated
Comment on lines +65 to +67
// PureVPNLocationCodes filters PureVPN servers by deterministic
// location code parsed from the hostname prefix (for example usca, ukm).
PureVPNLocationCodes []string `json:"purevpn_location_codes"`

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sounds like we could use the Hostnames field then

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed by removing PureVPNLocationCodes from this PR. Deterministic per-host selection remains available through the existing Hostnames field.

return fmt.Errorf("%w: %w", ErrCountryNotValid, err)
}

err = atLeastOneIsOneOfCaseInsensitive(settings.Regions, filterChoices.Regions, warner)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

regions is used for nordvpn. I get AI makes it easier to rewrite stuff, but please at the very least read changes made and verify a bit. Otherwise it adds excessive load for me to review these, especially with the increasing amount of AI generated PRs.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey sorry I was a bit moody on the AI-generated PR; if you're not a Go developer, then I 100% appreciate the gesture, and I'll comment on what's to change and the AI can then adapt it. However if you're a Go dev, I would definitely appreciate you reading up on the changes first 😉 !

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No worries - I'm def not a Go dev - my specialty in infra bash/terraform/NodeJS. My understanding was that I stopped using the regions field for PureVPN, but it was still being used for any other provider. Currently doing another scan to doublecheck, but unfortunately I don't have another set of creds with another provider other than PureVPN to confirm.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm trying to figure out a way to populate regions for PureVPN but I think I end up getting ratelimited by the IP geolocation APIs due to the sheer number of servers that need to be looked up.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed. Region validation remains for non-PureVPN providers (including NordVPN) and the broader region logic has been restored. For PureVPN specifically, I now ignore SERVER_REGIONS at validation time to avoid impacting PureVPN selection.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Appreciate it. I went through each requested change line-by-line, restored the non-PureVPN region behavior, and split out the extra PureVPN filtering feature work from this PR.

Comment thread internal/configuration/settings/serverselection.go Outdated
Comment thread internal/openvpn/logs_test.go Outdated

const url = "https://d11a57lttb2ffq.cloudfront.net/heartbleed/router/Recommended-CA2.zip"
contents, err := u.unzipper.FetchAndExtract(ctx, url)
debContent, err := fetchURL(ctx, http.DefaultClient, debURL)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use the u.client instead of http.DefaultClient. If not present, inject it into the Updater New function

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed. I injected the HTTP client into the PureVPN updater (Updater now has client *http.Client), defaulting to http.DefaultClient only if nil. .deb URL/content and inventory fetches now use u.client.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do not default it, it is never nil.

@reedog117

Copy link
Copy Markdown
Author

[Generated by Codex]

Summary of review feedback addressed:

  • Reverted OpenVPN TLS help text change and restored SERVER_REGIONS wording in:
    • internal/openvpn/logs.go
    • internal/openvpn/logs_test.go
  • Restored Surfshark retro-compatibility logic in getLocationFilterChoices.
  • Restored region validation/filtering behavior for non-PureVPN providers (including NordVPN).
  • Kept SERVER_REGIONS parsing in read() for compatibility, and now ignore regions only when provider is PureVPN during validation.
  • Removed the extra PureVPN filtering feature set (PureVPNServerTypes, PureVPNCountryCodes, PureVPNLocationCodes) from this PR so it can be split separately.
  • Updated PureVPN updater networking to use injected client (u.client) instead of direct http.DefaultClient calls:
    • Added client *http.Client to updater
    • Default to http.DefaultClient only if nil
    • Switched .deb and inventory fetches to u.client

Validation done locally:

  • go test ./internal/configuration/settings ./internal/provider/purevpn/... ./internal/provider/utils ./internal/storage ./internal/openvpn
  • go build ./cmd/gluetun
  • Real containerized PureVPN UDP connect test with credentials:
    • OpenVPN reached Initialization Sequence Completed
    • In-container egress IP checks confirmed VPN-routed traffic (distinct from host IP)

@reedog117

Copy link
Copy Markdown
Author

[Generated by Codex]\n\nI split the selector work into a dedicated stacked PR based on :\n- https://github.com/reedog117/gluetun/pull/1\n\nThis contains the PureVPN selector functionality (, , , , country/location code selectors) separated from the base updater changes.

@reedog117

Copy link
Copy Markdown
Author

[Generated by Codex]

I split the selector work into a dedicated stacked PR based on codex/purevpn-deb-updater:

This contains the PureVPN selector functionality (p2p, quantumresistant, obfuscation, portforwarding, and country/location code selectors) separated from the base updater changes.

@reedog117

Copy link
Copy Markdown
Author

[Generated by Codex]

I split the selector work into a dedicated stacked PR based on codex/purevpn-deb-updater:

This contains the PureVPN selector functionality (p2p, quantumresistant, obfuscation, portforwarding, and country/location code selectors) separated from the base updater changes.

This PR #3165 needs to be merged in before I can bring the selector functionality in as a separate PR as it's dependent on the new server inventory selection methods from here.

@reedog117

Copy link
Copy Markdown
Author

@qdm12 what additional steps are needed at this point to merge this and then i can bring in the newer selector functionality?

@reedog117 reedog117 requested a review from qdm12 March 28, 2026 21:43

@qdm12 qdm12 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! More comments to address, I'll review one last time after that. It's a bit of a huge PR, so it takes time to review 😉

Comment on lines +48 to +53
if testCase.err == nil {
require.NoError(t, err)
return
}
require.Error(t, err)
assert.ErrorIs(t, err, testCase.err)

@qdm12 qdm12 Apr 23, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same thing as

Suggested change
if testCase.err == nil {
require.NoError(t, err)
return
}
require.Error(t, err)
assert.ErrorIs(t, err, testCase.err)
assert.ErrorIs(t, err, testCase.err)

provider string
err error
}{
"purevpn default selection is valid": {

@qdm12 qdm12 Apr 23, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit for all test cases, use underscores instead of spaces, because Go tooling replaces spaces with underscores in its logs, so it's easier to find the subtest name failing with copy and paste.

Comment on lines +107 to +109
// Keep parsing SERVER_REGIONS for retro-compatibility, but
// do not apply it to PureVPN filtering.
ss.Regions = nil

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not? It should still be applied somehow, otherwise it breaks compatibility.

Hostname: "hostname",
TCP: true,
UDP: true,
OvpnX509: "x509",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add TCPPorts and UDPPorts fields. Make sure order of the elements in each slice does not matter when comparing two servers.

return nil, fmt.Errorf("decompressing %s: %w", dataTarName, err)
}

asarContent, err = extractFileFromTar(dataTar, pureVPNAsarPath)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

inline the constant pureVPNAsarPath right above this line, there is no need to have at global scope if it's used only here.

connection models.Connection, err error,
) {
defaults := utils.NewConnectionDefaults(80, 53, 0) //nolint:mnd
defaults := utils.NewConnectionDefaults(80, 15021, 0)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RE-ADD //nolint:mnd

}
}

return portForward, quantumResistant, obfuscated, p2p

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's the difference between portForward and p2p?


// Pulling from the largest geographies first tends to recover all active
// configuration versions with fewer requests.
prioritized := []string{"us", "uk", "de", "fr", "nl", "sg", "jp", "au", "ca"}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

supernit

Suggested change
prioritized := []string{"us", "uk", "de", "fr", "nl", "sg", "jp", "au", "ca"}
prioritized := [...]string{"us", "uk", "de", "fr", "nl", "sg", "jp", "au", "ca"}

func enrichLocationBlanks(ctx context.Context, ipFetcher common.IPFetcher, warner common.Warner, servers []models.Server) {
if ipFetcher == nil || !ipFetcher.CanFetchAnyIP() {
return
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keep the check as it was in the calling function:

	if !u.ipFetcher.CanFetchAnyIP() {
		return nil, fmt.Errorf("%w: %s", common.ErrIPFetcherUnsupported, u.ipFetcher.String())
	}

parsedCountry, parsedCity, warnings := parseHostname(servers[i].Hostname)
for _, warning := range warnings {
u.warner.Warn(warning)
if !needsGeolocationEnrichment(servers[i]) || len(servers[i].IPs) == 0 {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this trigger often? Asking because ideally I would like to get rid of the ipFetcher entirely, purevpn is the only provider using it for its updater. Hopefully we can figure out enough data without it.

@reedog117

Copy link
Copy Markdown
Author

Thanks! More comments to address, I'll review one last time after that. It's a bit of a huge PR, so it takes time to review 😉

Awesome - will work on this in the next few days

@qdm12 qdm12 force-pushed the master branch 4 times, most recently from 40f126b to 44d5104 Compare May 3, 2026 04:29
@qdm12

qdm12 commented May 10, 2026

Copy link
Copy Markdown
Member

I actually need this rather fast, this is blocking #3301 which I would like to finish soon (because purevpn is the only one using the public ip fetcher and this makes package imports shitty basically)
I'll circle back in a few days and comment here, I will pick up the PR if you didn't get the time to plunge back by then

@qdm12

qdm12 commented Jun 11, 2026

Copy link
Copy Markdown
Member

FYI working on it now

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: gluetun is always trying port 53 for connection when downloaded OpenVPN files from PureVPN reference port 1194.

2 participants