Compare commits
131 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b9a9319cb4 | ||
|
|
77e4317135 | ||
|
|
b10d97e53a | ||
|
|
648a4c04d7 | ||
|
|
3ca674dca7 | ||
|
|
fa97fd496e | ||
|
|
c76a7ee8da | ||
|
|
80f6b78332 | ||
|
|
8dc54a7c44 | ||
|
|
8f080c537b | ||
|
|
427cf86f44 | ||
|
|
2d244c08e7 | ||
|
|
82c0f523aa | ||
|
|
c07a0b0ada | ||
|
|
e4c306c0ee | ||
|
|
6ffb94f819 | ||
|
|
142238e8b7 | ||
|
|
678e23c7d6 | ||
|
|
0abcebe1d8 | ||
|
|
f398af1169 | ||
|
|
afbea415e3 | ||
|
|
225bd5d25b | ||
|
|
3651cc6161 | ||
|
|
dc674014ff | ||
|
|
0e0e03949d | ||
|
|
f5bf5c236a | ||
|
|
94480ecabb | ||
|
|
31ef9b1d45 | ||
|
|
bf76132fd4 | ||
|
|
8cc2983318 | ||
|
|
caeca18ed7 | ||
|
|
50febb41ff | ||
|
|
79293e067c | ||
|
|
f45be80591 | ||
|
|
d405ba8dca | ||
|
|
ca975b1c01 | ||
|
|
e0e3ca3832 | ||
|
|
e7c952cbf7 | ||
|
|
85ad2dd39a | ||
|
|
0c4f0ec17b | ||
|
|
5ad4136955 | ||
|
|
a432de95a9 | ||
|
|
1d25a0e18c | ||
|
|
29fd95685f | ||
|
|
62a6016882 | ||
|
|
18a4a79763 | ||
|
|
56ea722f93 | ||
|
|
d2ab974933 | ||
|
|
37d7a8b5fe | ||
|
|
e4dcadd825 | ||
|
|
fee99e9fe3 | ||
|
|
8ac4826126 | ||
|
|
7deb12e06d | ||
|
|
d6e218141b | ||
|
|
f44121b044 | ||
|
|
5d8d92462d | ||
|
|
985cf7b7dd | ||
|
|
dcbc10fd57 | ||
|
|
79f243e98d | ||
|
|
cf95692b93 | ||
|
|
d8e008606f | ||
|
|
3cd26a9f61 | ||
|
|
5d74320ee7 | ||
|
|
f9aadeef1c | ||
|
|
625de1c834 | ||
|
|
1c0a3ed1a4 | ||
|
|
03ba9169f4 | ||
|
|
c22e0e9db7 | ||
|
|
6bcbaf085d | ||
|
|
9a1d9c5d74 | ||
|
|
59a3a072e0 | ||
|
|
9f001bbc06 | ||
|
|
b8356b60a6 | ||
|
|
e2e218c74b | ||
|
|
3bf23cbae5 | ||
|
|
da562d8206 | ||
|
|
81bf83db13 | ||
|
|
7a25dcd130 | ||
|
|
877c7e1a9f | ||
|
|
77b2512745 | ||
|
|
749b73ef15 | ||
|
|
e499eca12c | ||
|
|
80f25c34e5 | ||
|
|
61677fbce2 | ||
|
|
dc6171185e | ||
|
|
f7e4331e93 | ||
|
|
1340511b64 | ||
|
|
c3078f84e8 | ||
|
|
9f65157a0d | ||
|
|
89166cdabf | ||
|
|
b872973e8b | ||
|
|
2000e72357 | ||
|
|
836e53642d | ||
|
|
af3f882bb8 | ||
|
|
2ab05b9350 | ||
|
|
1022eb8a6e | ||
|
|
15fe62de32 | ||
|
|
83d87f83f9 | ||
|
|
76a0c1f6c4 | ||
|
|
a1588302a7 | ||
|
|
91ce790b6b | ||
|
|
5d3982c2d2 | ||
|
|
2cf7f7b268 | ||
|
|
8645d978ba | ||
|
|
cc18b158f4 | ||
|
|
0730b6db6e | ||
|
|
3d2a360401 | ||
|
|
0c60dab384 | ||
|
|
f5f0ad7f28 | ||
|
|
f807f756eb | ||
|
|
11af6c10f1 | ||
|
|
40342619e7 | ||
|
|
19bf62c21f | ||
|
|
2ea00d149f | ||
|
|
cc677bde93 | ||
|
|
6627cda96c | ||
|
|
cade2732b0 | ||
|
|
541a4a3271 | ||
|
|
0eccd068e5 | ||
|
|
87f4b9e422 | ||
|
|
bcaf2e42fd | ||
|
|
d39201f9b3 | ||
|
|
8ac2a816c3 | ||
|
|
344f1bf9ee | ||
|
|
f0a006fc43 | ||
|
|
145da0b21d | ||
|
|
094de89a3e | ||
|
|
65ace12def | ||
|
|
9afe455635 | ||
|
|
45ce422a89 | ||
|
|
4a0738cd49 |
@@ -8,7 +8,7 @@
|
||||
"vscode"
|
||||
],
|
||||
"shutdownAction": "stopCompose",
|
||||
"postCreateCommand": "source ~/.windows.sh && go mod download && go mod tidy",
|
||||
"postCreateCommand": "~/.windows.sh && go mod download && go mod tidy",
|
||||
"workspaceFolder": "/workspace",
|
||||
"extensions": [
|
||||
"golang.go",
|
||||
|
||||
49
.github/ISSUE_TEMPLATE/bug.md
vendored
49
.github/ISSUE_TEMPLATE/bug.md
vendored
@@ -1,49 +0,0 @@
|
||||
---
|
||||
name: Bug
|
||||
about: Report a bug
|
||||
title: 'Bug: FILL THIS TEXT!'
|
||||
labels: ":bug: bug"
|
||||
assignees: qdm12
|
||||
|
||||
---
|
||||
|
||||
<!---
|
||||
⚠️ Answer the following or I'll insta-close your issue
|
||||
-->
|
||||
|
||||
**Is this urgent?**: No
|
||||
|
||||
**Host OS** (approximate answer is fine too): Ubuntu 18
|
||||
|
||||
<!---
|
||||
🚧 If this is about the Unraid template see https://github.com/qdm12/gluetun/discussions/550
|
||||
-->
|
||||
|
||||
**CPU arch** or **device name**: amd64
|
||||
|
||||
**What VPN provider are you using**:
|
||||
|
||||
**What are you using to run your container?**: Docker Compose
|
||||
|
||||
**What is the version of the program** (See the line at the top of your logs)
|
||||
|
||||
```
|
||||
Running version latest built on 2020-03-13T01:30:06Z (commit d0f678c)
|
||||
```
|
||||
|
||||
**What's the problem** 🤔
|
||||
|
||||
That feature doesn't work
|
||||
|
||||
**Share your logs... (careful to remove in example tokens)**
|
||||
|
||||
```log
|
||||
|
||||
PASTE YOUR LOGS
|
||||
IN THERE
|
||||
|
||||
```
|
||||
|
||||
<!---
|
||||
💡 You can highlight your code with https://docs.github.com/en/github/writing-on-github/working-with-advanced-formatting/creating-and-highlighting-code-blocks#syntax-highlight
|
||||
-->
|
||||
107
.github/ISSUE_TEMPLATE/bug.yml
vendored
Normal file
107
.github/ISSUE_TEMPLATE/bug.yml
vendored
Normal file
@@ -0,0 +1,107 @@
|
||||
name: Bug
|
||||
description: Report a bug
|
||||
title: "Bug: "
|
||||
labels: [":bug: bug"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to fill out this bug report!
|
||||
- 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.
|
||||
options:
|
||||
- "No"
|
||||
- "Yes"
|
||||
- type: input
|
||||
id: host-os
|
||||
attributes:
|
||||
label: Host OS
|
||||
description: What is your host OS?
|
||||
placeholder: "Debian Buster"
|
||||
- type: dropdown
|
||||
id: cpu-arch
|
||||
attributes:
|
||||
label: CPU arch
|
||||
description: You can find it on Linux with `uname -m`.
|
||||
options:
|
||||
- x86_64
|
||||
- aarch64
|
||||
- armv7l
|
||||
- "386"
|
||||
- s390x
|
||||
- ppc64le
|
||||
- type: dropdown
|
||||
id: vpn-service-provider
|
||||
attributes:
|
||||
label: VPN service provider
|
||||
options:
|
||||
- Custom
|
||||
- Cyberghost
|
||||
- ExpressVPN
|
||||
- FastestVPN
|
||||
- HideMyAss
|
||||
- IPVanish
|
||||
- IVPN
|
||||
- Mullvad
|
||||
- NordVPN
|
||||
- Privado
|
||||
- Private Internet Access
|
||||
- PrivateVPN
|
||||
- ProtonVPN
|
||||
- PureVPN
|
||||
- Surfshark
|
||||
- TorGuard
|
||||
- VPNUnlimited
|
||||
- VyprVPN
|
||||
- WeVPN
|
||||
- Windscribe
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: docker
|
||||
attributes:
|
||||
label: What are you using to run the container
|
||||
options:
|
||||
- docker run
|
||||
- docker-compose
|
||||
- Portainer
|
||||
- Kubernetes
|
||||
- Podman
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
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)`.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: problem
|
||||
attributes:
|
||||
label: "What's the problem 🤔"
|
||||
placeholder: "That feature does not work..."
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: Share your logs
|
||||
description: No sensitive information is logged out except when running with `LOG_LEVEL=debug`.
|
||||
render: log
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: config
|
||||
attributes:
|
||||
label: Share your configuration
|
||||
description: Share your configuration such as `docker-compose.yml`. Ensure to remove credentials.
|
||||
render: yml
|
||||
7
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
7
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
contact_links:
|
||||
- name: Configuration help?
|
||||
url: https://github.com/qdm12/gluetun/discussions/new
|
||||
about: Please create a Github discussion.
|
||||
- name: Unraid template issue
|
||||
url: https://github.com/qdm12/gluetun/discussions/550
|
||||
about: Please read the relevant Github discussion.
|
||||
17
.github/ISSUE_TEMPLATE/feature_request.md
vendored
17
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,17 +0,0 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest a feature to add to this project
|
||||
title: 'Feature request: FILL THIS TEXT!'
|
||||
labels: ":bulb: feature request"
|
||||
assignees: qdm12
|
||||
|
||||
---
|
||||
|
||||
**What's the feature?** 🧐
|
||||
|
||||
- Support this new feature because that and that
|
||||
|
||||
**Optional extra information** 🚀
|
||||
|
||||
- I tried `docker run something` and it doesn't work
|
||||
- That [url](https://github.com/qdm12/gluetun) is interesting
|
||||
19
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
19
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
name: Feature request
|
||||
description: Suggest a feature to add to Gluetun
|
||||
title: "Feature request: "
|
||||
labels: [":bulb: feature request"]
|
||||
body:
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: "What's the feature 🧐"
|
||||
placeholder: "Make the tunnel resistant to earth quakes"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: extra
|
||||
attributes:
|
||||
label: "Extra information and references"
|
||||
placeholder: |
|
||||
- I tried `docker run something` and it doesn't work
|
||||
- That [url](https://github.com/qdm12/gluetun) is interesting
|
||||
67
.github/ISSUE_TEMPLATE/help.md
vendored
67
.github/ISSUE_TEMPLATE/help.md
vendored
@@ -1,67 +0,0 @@
|
||||
---
|
||||
name: Help
|
||||
about: Ask for help
|
||||
title: 'Help: FILL THIS TEXT!'
|
||||
labels: ":pray: help wanted"
|
||||
assignees:
|
||||
|
||||
---
|
||||
|
||||
<!---
|
||||
⚠️ If this about a Docker configuration problem or another service:
|
||||
Start a discussion at https://github.com/qdm12/gluetun/discussions/new
|
||||
OR I WILL INSTA-CLOSE YOUR ISSUE.
|
||||
-->
|
||||
|
||||
<!---
|
||||
⚠️ Answer the following or I'll insta-close your issue
|
||||
-->
|
||||
|
||||
**Is this urgent?**: No
|
||||
|
||||
**Host OS** (approximate answer is fine too): Ubuntu 18
|
||||
|
||||
**CPU arch** or **device name**: amd64
|
||||
|
||||
**What VPN provider are you using**:
|
||||
|
||||
**What is the version of the program** (See the line at the top of your logs)
|
||||
|
||||
```
|
||||
Running version latest built on 2020-03-13T01:30:06Z (commit d0f678c)
|
||||
```
|
||||
|
||||
**What's the problem** 🤔
|
||||
|
||||
That feature doesn't work
|
||||
|
||||
**Share your logs... (careful to remove in example tokens)**
|
||||
|
||||
```log
|
||||
|
||||
PASTE YOUR LOGS
|
||||
IN THERE
|
||||
|
||||
```
|
||||
|
||||
**What are you using to run your container?**: Docker Compose
|
||||
|
||||
<!---
|
||||
💡 You can highlight your code with https://docs.github.com/en/github/writing-on-github/working-with-advanced-formatting/creating-and-highlighting-code-blocks#syntax-highlight
|
||||
-->
|
||||
|
||||
Please also share your configuration file:
|
||||
|
||||
```yml
|
||||
your .yml
|
||||
content
|
||||
in here
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```sh
|
||||
# your docker
|
||||
# run command
|
||||
# in here
|
||||
```
|
||||
18
.github/ISSUE_TEMPLATE/wiki issue.yml
vendored
Normal file
18
.github/ISSUE_TEMPLATE/wiki issue.yml
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
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
|
||||
17
.github/labels.yml
vendored
17
.github/labels.yml
vendored
@@ -14,6 +14,14 @@
|
||||
color: "795548"
|
||||
description: ""
|
||||
|
||||
# Priority
|
||||
- name: "🚨 Urgent"
|
||||
color: "d5232f"
|
||||
description: ""
|
||||
- name: "💤 Low priority"
|
||||
color: "4285f4"
|
||||
description: ""
|
||||
|
||||
# VPN providers
|
||||
- name: ":cloud: Cyberghost"
|
||||
color: "cfe8d4"
|
||||
@@ -27,6 +35,9 @@
|
||||
- name: ":cloud: IVPN"
|
||||
color: "cfe8d4"
|
||||
description: ""
|
||||
- name: ":cloud: ExpressVPN"
|
||||
color: "cfe8d4"
|
||||
description: ""
|
||||
- name: ":cloud: FastestVPN"
|
||||
color: "cfe8d4"
|
||||
description: ""
|
||||
@@ -36,6 +47,9 @@
|
||||
- name: ":cloud: NordVPN"
|
||||
color: "cfe8d4"
|
||||
description: ""
|
||||
- name: ":cloud: Perfect Privacy"
|
||||
color: "cfe8d4"
|
||||
description: ""
|
||||
- name: ":cloud: PIA"
|
||||
color: "cfe8d4"
|
||||
description: ""
|
||||
@@ -62,6 +76,9 @@
|
||||
- name: ":cloud: Vyprvpn"
|
||||
color: "cfe8d4"
|
||||
description: ""
|
||||
- name: ":cloud: WeVPN"
|
||||
color: "cfe8d4"
|
||||
description: ""
|
||||
- name: ":cloud: Windscribe"
|
||||
color: "cfe8d4"
|
||||
description: ""
|
||||
|
||||
109
.github/workflows/ci.yml
vendored
109
.github/workflows/ci.yml
vendored
@@ -1,6 +1,24 @@
|
||||
name: CI
|
||||
on:
|
||||
release:
|
||||
types:
|
||||
- published
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- .github/workflows/ci.yml
|
||||
- cmd/**
|
||||
- internal/**
|
||||
- pkg/**
|
||||
- .dockerignore
|
||||
- .golangci.yml
|
||||
- Dockerfile
|
||||
- go.mod
|
||||
- go.sum
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- .github/workflows/ci.yml
|
||||
- cmd/**
|
||||
@@ -14,11 +32,17 @@ on:
|
||||
|
||||
jobs:
|
||||
verify:
|
||||
# Only run if it's a push event or if it's a PR from this repository, and it is not dependabot.
|
||||
if: |
|
||||
github.actor != 'dependabot[bot]' &&
|
||||
(github.event_name == 'push' ||
|
||||
github.event_name == 'release' ||
|
||||
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository))
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
DOCKER_BUILDKIT: "1"
|
||||
steps:
|
||||
- uses: actions/checkout@v2.3.4
|
||||
- uses: actions/checkout@v2.4.0
|
||||
|
||||
- name: Linting
|
||||
run: docker build --target lint .
|
||||
@@ -44,18 +68,23 @@ jobs:
|
||||
- name: Build final image
|
||||
run: docker build -t final-image .
|
||||
|
||||
- name: Image security analysis
|
||||
uses: snyk/actions/docker@master
|
||||
env:
|
||||
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
|
||||
with:
|
||||
image: final-image
|
||||
# - name: Image security analysis
|
||||
# uses: snyk/actions/docker@master
|
||||
# env:
|
||||
# SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
|
||||
# with:
|
||||
# image: final-image
|
||||
|
||||
publish:
|
||||
# Only run if it's a push event or if it's a PR from this repository
|
||||
if: |
|
||||
github.event_name == 'push' ||
|
||||
github.event_name == 'release' ||
|
||||
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository)
|
||||
needs: [verify]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2.3.4
|
||||
- uses: actions/checkout@v2.4.0
|
||||
|
||||
- uses: docker/setup-qemu-action@v1
|
||||
- uses: docker/setup-buildx-action@v1
|
||||
@@ -65,35 +94,51 @@ jobs:
|
||||
username: qmcgaw
|
||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
|
||||
- name: Set variables
|
||||
id: vars
|
||||
env:
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
- name: Check for semver tag
|
||||
id: semvercheck
|
||||
run: |
|
||||
BRANCH=${GITHUB_REF#refs/heads/}
|
||||
TAG=${GITHUB_REF#refs/tags/}
|
||||
echo ::set-output name=commit::$(git rev-parse --short HEAD)
|
||||
echo ::set-output name=created::$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
||||
if [ "$TAG" != "$GITHUB_REF" ]; then
|
||||
echo ::set-output name=version::$TAG
|
||||
echo ::set-output name=platforms::linux/amd64,linux/386,linux/arm64,linux/arm/v6,linux/arm/v7,linux/ppc64le
|
||||
elif [ "$BRANCH" = "master" ]; then
|
||||
echo ::set-output name=version::latest
|
||||
echo ::set-output name=platforms::linux/amd64,linux/386,linux/arm64,linux/arm/v6,linux/arm/v7,linux/ppc64le
|
||||
if [[ ${{ github.ref }} =~ ^refs/tags/v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
MATCH=true
|
||||
else
|
||||
echo ::set-output name=version::$BRANCH
|
||||
echo ::set-output name=platforms::linux/amd64,linux/386,linux/arm64,linux/arm/v6,linux/arm/v7,linux/ppc64le
|
||||
MATCH=false
|
||||
fi
|
||||
if [[ ! ${{ github.ref }} =~ ^refs/tags/v0\. ]]; then
|
||||
MATCH=$MATCH_nonzero
|
||||
fi
|
||||
echo ::set-output name=match::$MATCH
|
||||
|
||||
# extract metadata (tags, labels) for Docker
|
||||
# https://github.com/docker/metadata-action
|
||||
- name: Extract Docker metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v3
|
||||
with:
|
||||
flavor: |
|
||||
latest=${{ github.ref == 'refs/heads/master' }}
|
||||
images: |
|
||||
qmcgaw/gluetun
|
||||
qmcgaw/private-internet-access
|
||||
tags: |
|
||||
type=ref,event=branch,enable=${{ github.ref != 'refs/heads/master' }}
|
||||
type=ref,event=pr
|
||||
type=ref,event=tag,enable=${{ !startsWith(steps.semvercheck.outputs.match, 'true') }}
|
||||
type=semver,pattern=v{{major}}.{{minor}}.{{patch}},enable=${{ startsWith(steps.semvercheck.outputs.match, 'true') }}
|
||||
type=semver,pattern=v{{major}}.{{minor}},enable=${{ startsWith(steps.semvercheck.outputs.match, 'true') }}
|
||||
type=semver,pattern=v{{major}},enable=${{ startsWith(steps.semvercheck.outputs.match, 'true_nonzero') }}
|
||||
type=raw,value=latest,enable=${{ !startsWith(steps.semvercheck.outputs.match, 'true') }}
|
||||
|
||||
- name: Short commit
|
||||
id: shortcommit
|
||||
run: echo "::set-output name=value::$(git rev-parse --short HEAD)"
|
||||
|
||||
- name: Build and push final image
|
||||
uses: docker/build-push-action@v2.6.1
|
||||
uses: docker/build-push-action@v2.7.0
|
||||
with:
|
||||
platforms: ${{ steps.vars.outputs.platforms }}
|
||||
platforms: linux/amd64,linux/386,linux/arm64,linux/arm/v6,linux/arm/v7,linux/ppc64le
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
build-args: |
|
||||
CREATED=${{ steps.vars.outputs.created }}
|
||||
COMMIT=${{ steps.vars.outputs.commit }}
|
||||
VERSION=${{ steps.vars.outputs.version }}
|
||||
tags: |
|
||||
qmcgaw/gluetun:${{ steps.vars.outputs.version }}
|
||||
qmcgaw/private-internet-access:${{ steps.vars.outputs.version }}
|
||||
CREATED=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.created'] }}
|
||||
COMMIT=${{ steps.shortcommit.outputs.value }}
|
||||
VERSION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
push: true
|
||||
|
||||
37
.github/workflows/dependabot.yml
vendored
Normal file
37
.github/workflows/dependabot.yml
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
name: Dependabot
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- .github/workflows/dependabot.yml
|
||||
- cmd/**
|
||||
- internal/**
|
||||
- pkg/**
|
||||
- .dockerignore
|
||||
- .golangci.yml
|
||||
- Dockerfile
|
||||
- go.mod
|
||||
- go.sum
|
||||
|
||||
jobs:
|
||||
verify:
|
||||
if: ${{ github.actor == 'dependabot[bot]' }}
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
DOCKER_BUILDKIT: "1"
|
||||
steps:
|
||||
- uses: actions/checkout@v2.4.0
|
||||
|
||||
- name: Build test image
|
||||
run: docker build --target test -t test-container .
|
||||
|
||||
- name: Run tests in test container
|
||||
run: |
|
||||
touch coverage.txt
|
||||
docker run --rm \
|
||||
-v "$(pwd)/coverage.txt:/tmp/gobuild/coverage.txt" \
|
||||
test-container
|
||||
|
||||
- name: Build final image
|
||||
run: docker build -t final-image .
|
||||
2
.github/workflows/dockerhub-description.yml
vendored
2
.github/workflows/dockerhub-description.yml
vendored
@@ -10,7 +10,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2.3.4
|
||||
uses: actions/checkout@v2.4.0
|
||||
- name: Docker Hub Description
|
||||
uses: peter-evans/dockerhub-description@v2
|
||||
with:
|
||||
|
||||
40
.github/workflows/fork.yml
vendored
Normal file
40
.github/workflows/fork.yml
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
name: Fork
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- .github/workflows/fork.yml
|
||||
- cmd/**
|
||||
- internal/**
|
||||
- pkg/**
|
||||
- .dockerignore
|
||||
- .golangci.yml
|
||||
- Dockerfile
|
||||
- go.mod
|
||||
- go.sum
|
||||
|
||||
jobs:
|
||||
verify:
|
||||
if: github.event.pull_request.head.repo.full_name != github.repository && github.actor != 'dependabot[bot]'
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
DOCKER_BUILDKIT: "1"
|
||||
steps:
|
||||
- uses: actions/checkout@v2.4.0
|
||||
|
||||
- name: Linting
|
||||
run: docker build --target lint .
|
||||
|
||||
- name: Build test image
|
||||
run: docker build --target test -t test-container .
|
||||
|
||||
- name: Run tests in test container
|
||||
run: |
|
||||
touch coverage.txt
|
||||
docker run --rm \
|
||||
-v "$(pwd)/coverage.txt:/tmp/gobuild/coverage.txt" \
|
||||
test-container
|
||||
|
||||
- name: Build final image
|
||||
run: docker build -t final-image .
|
||||
2
.github/workflows/labels.yml
vendored
2
.github/workflows/labels.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
||||
labeler:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2.3.4
|
||||
- uses: actions/checkout@v2.4.0
|
||||
- uses: crazy-max/ghaction-github-labeler@v3
|
||||
with:
|
||||
yaml-file: .github/labels.yml
|
||||
|
||||
2
.github/workflows/misspell.yml
vendored
2
.github/workflows/misspell.yml
vendored
@@ -8,7 +8,7 @@ jobs:
|
||||
misspell:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2.3.4
|
||||
- uses: actions/checkout@v2.4.0
|
||||
- uses: reviewdog/action-misspell@v1
|
||||
with:
|
||||
locale: "US"
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
linters-settings:
|
||||
maligned:
|
||||
suggest-new: true
|
||||
misspell:
|
||||
locale: US
|
||||
|
||||
@@ -30,16 +28,22 @@ issues:
|
||||
linters:
|
||||
- gomnd
|
||||
linters:
|
||||
disable-all: true
|
||||
enable:
|
||||
# - cyclop
|
||||
# - errorlint
|
||||
# - ireturn
|
||||
# - varnamelen
|
||||
# - wrapcheck
|
||||
- asciicheck
|
||||
- bidichk
|
||||
- bodyclose
|
||||
- deadcode
|
||||
- dogsled
|
||||
- dupl
|
||||
- errcheck
|
||||
- durationcheck
|
||||
- errname
|
||||
- exhaustive
|
||||
- exportloopref
|
||||
- forcetypeassert
|
||||
- gci
|
||||
- gochecknoglobals
|
||||
- gochecknoinits
|
||||
@@ -52,33 +56,33 @@ linters:
|
||||
- goheader
|
||||
- goimports
|
||||
- gomnd
|
||||
- gomoddirectives
|
||||
- goprintffuncname
|
||||
- gosec
|
||||
- gosimple
|
||||
- govet
|
||||
- ifshort
|
||||
- importas
|
||||
- ineffassign
|
||||
- lll
|
||||
- makezero
|
||||
- misspell
|
||||
- nakedret
|
||||
- nestif
|
||||
- nilerr
|
||||
- nilnil
|
||||
- noctx
|
||||
- nolintlint
|
||||
- prealloc
|
||||
- predeclared
|
||||
- predeclared
|
||||
- promlinter
|
||||
- revive
|
||||
- rowserrcheck
|
||||
- sqlclosecheck
|
||||
- staticcheck
|
||||
- structcheck
|
||||
- tenv
|
||||
- thelper
|
||||
- tparallel
|
||||
- typecheck
|
||||
- unconvert
|
||||
- unparam
|
||||
- unused
|
||||
- varcheck
|
||||
- wastedassign
|
||||
- whitespace
|
||||
|
||||
run:
|
||||
|
||||
81
Dockerfile
81
Dockerfile
@@ -2,7 +2,7 @@ ARG ALPINE_VERSION=3.14
|
||||
ARG GO_ALPINE_VERSION=3.14
|
||||
ARG GO_VERSION=1.17
|
||||
ARG XCPUTRANSLATE_VERSION=v0.6.0
|
||||
ARG GOLANGCI_LINT_VERSION=v1.41.1
|
||||
ARG GOLANGCI_LINT_VERSION=v1.43.0
|
||||
ARG BUILDPLATFORM=linux/amd64
|
||||
|
||||
FROM --platform=${BUILDPLATFORM} qmcgaw/xcputranslate:${XCPUTRANSLATE_VERSION} AS xcputranslate
|
||||
@@ -67,61 +67,68 @@ LABEL \
|
||||
org.opencontainers.image.title="VPN swiss-knife like client for multiple VPN providers" \
|
||||
org.opencontainers.image.description="VPN swiss-knife like client to tunnel to multiple VPN servers using OpenVPN, IPtables, DNS over TLS, Shadowsocks, an HTTP proxy and Alpine Linux"
|
||||
ENV VPNSP=pia \
|
||||
VERSION_INFORMATION=on \
|
||||
LOG_LEVEL=info \
|
||||
VPN_TYPE=openvpn \
|
||||
PROTOCOL=udp \
|
||||
# OpenVPN
|
||||
OPENVPN_PROTOCOL=udp \
|
||||
OPENVPN_USER= \
|
||||
OPENVPN_PASSWORD= \
|
||||
OPENVPN_USER_SECRETFILE=/run/secrets/openvpn_user \
|
||||
OPENVPN_PASSWORD_SECRETFILE=/run/secrets/openvpn_password \
|
||||
OPENVPN_VERSION=2.5 \
|
||||
OPENVPN_VERBOSITY=1 \
|
||||
OPENVPN_FLAGS= \
|
||||
OPENVPN_CIPHER= \
|
||||
OPENVPN_AUTH= \
|
||||
OPENVPN_ROOT=yes \
|
||||
OPENVPN_TARGET_IP= \
|
||||
OPENVPN_IPV6=off \
|
||||
OPENVPN_CUSTOM_CONFIG= \
|
||||
OPENVPN_INTERFACE=tun0 \
|
||||
OPENVPN_PORT= \
|
||||
# Wireguard
|
||||
WIREGUARD_PRIVATE_KEY= \
|
||||
WIREGUARD_PRESHARED_KEY= \
|
||||
WIREGUARD_PUBLIC_KEY= \
|
||||
WIREGUARD_ADDRESS= \
|
||||
WIREGUARD_PORT= \
|
||||
WIREGUARD_ENDPOINT_IP= \
|
||||
WIREGUARD_ENDPOINT_PORT= \
|
||||
WIREGUARD_INTERFACE=wg0 \
|
||||
TZ= \
|
||||
PUID= \
|
||||
PGID= \
|
||||
PUBLICIP_FILE="/tmp/gluetun/ip" \
|
||||
# VPN provider settings
|
||||
OPENVPN_USER= \
|
||||
OPENVPN_PASSWORD= \
|
||||
USER_SECRETFILE=/run/secrets/openvpn_user \
|
||||
PASSWORD_SECRETFILE=/run/secrets/openvpn_password \
|
||||
# VPN server filtering
|
||||
REGION= \
|
||||
COUNTRY= \
|
||||
CITY= \
|
||||
PORT= \
|
||||
SERVER_HOSTNAME= \
|
||||
# Mullvad only:
|
||||
# # Mullvad only:
|
||||
ISP= \
|
||||
OWNED=no \
|
||||
# Private Internet Access only:
|
||||
# # Private Internet Access only:
|
||||
PIA_ENCRYPTION=strong \
|
||||
PORT_FORWARDING=off \
|
||||
PORT_FORWARDING_STATUS_FILE="/tmp/gluetun/forwarded_port" \
|
||||
# Cyberghost only:
|
||||
CYBERGHOST_GROUP="Premium UDP Europe" \
|
||||
# # Cyberghost only:
|
||||
OPENVPN_CLIENTCRT_SECRETFILE=/run/secrets/openvpn_clientcrt \
|
||||
OPENVPN_CLIENTKEY_SECRETFILE=/run/secrets/openvpn_clientkey \
|
||||
# Nordvpn only:
|
||||
# # Nordvpn only:
|
||||
SERVER_NUMBER= \
|
||||
# NordVPN and ProtonVPN only:
|
||||
# # PIA and ProtonVPN only:
|
||||
SERVER_NAME= \
|
||||
# ProtonVPN only:
|
||||
# # ProtonVPN only:
|
||||
FREE_ONLY= \
|
||||
# Openvpn
|
||||
OPENVPN_CIPHER= \
|
||||
OPENVPN_AUTH= \
|
||||
# # Surfshark only:
|
||||
MULTIHOP_ONLY= \
|
||||
# Firewall
|
||||
FIREWALL=on \
|
||||
FIREWALL_VPN_INPUT_PORTS= \
|
||||
FIREWALL_INPUT_PORTS= \
|
||||
FIREWALL_OUTBOUND_SUBNETS= \
|
||||
FIREWALL_DEBUG=off \
|
||||
# Logging
|
||||
LOG_LEVEL=info \
|
||||
# Health
|
||||
HEALTH_OPENVPN_DURATION_INITIAL=6s \
|
||||
HEALTH_OPENVPN_DURATION_ADDITION=5s \
|
||||
HEALTH_SERVER_ADDRESS=127.0.0.1:9999 \
|
||||
HEALTH_ADDRESS_TO_PING=github.com \
|
||||
HEALTH_VPN_DURATION_INITIAL=6s \
|
||||
HEALTH_VPN_DURATION_ADDITION=5s \
|
||||
# DNS over TLS
|
||||
DOT=on \
|
||||
DOT_PROVIDERS=cloudflare \
|
||||
@@ -138,12 +145,6 @@ ENV VPNSP=pia \
|
||||
DNS_UPDATE_PERIOD=24h \
|
||||
DNS_PLAINTEXT_ADDRESS=1.1.1.1 \
|
||||
DNS_KEEP_NAMESERVER=off \
|
||||
# Firewall
|
||||
FIREWALL=on \
|
||||
FIREWALL_VPN_INPUT_PORTS= \
|
||||
FIREWALL_INPUT_PORTS= \
|
||||
FIREWALL_OUTBOUND_SUBNETS= \
|
||||
FIREWALL_DEBUG=off \
|
||||
# HTTP proxy
|
||||
HTTPPROXY= \
|
||||
HTTPPROXY_LOG=off \
|
||||
@@ -159,7 +160,16 @@ ENV VPNSP=pia \
|
||||
SHADOWSOCKS_PASSWORD= \
|
||||
SHADOWSOCKS_PASSWORD_SECRETFILE=/run/secrets/shadowsocks_password \
|
||||
SHADOWSOCKS_CIPHER=chacha20-ietf-poly1305 \
|
||||
UPDATER_PERIOD=0
|
||||
# Server data updater
|
||||
UPDATER_PERIOD=0 \
|
||||
# Public IP
|
||||
PUBLICIP_FILE="/tmp/gluetun/ip" \
|
||||
PUBLICIP_PERIOD=12h \
|
||||
# Extras
|
||||
VERSION_INFORMATION=on \
|
||||
TZ= \
|
||||
PUID= \
|
||||
PGID=
|
||||
ENTRYPOINT ["/entrypoint"]
|
||||
EXPOSE 8000/tcp 8888/tcp 8388/tcp 8388/udp
|
||||
HEALTHCHECK --interval=5s --timeout=5s --start-period=10s --retries=1 CMD /entrypoint healthcheck
|
||||
@@ -169,6 +179,9 @@ RUN apk add --no-cache --update -l apk-tools && \
|
||||
mv /usr/sbin/openvpn /usr/sbin/openvpn2.4 && \
|
||||
apk del openvpn && \
|
||||
apk add --no-cache --update openvpn ca-certificates iptables ip6tables unbound tzdata && \
|
||||
mv /usr/sbin/openvpn /usr/sbin/openvpn2.5 && \
|
||||
# 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 && \
|
||||
deluser openvpn && \
|
||||
deluser unbound && \
|
||||
|
||||
107
README.md
107
README.md
@@ -1,9 +1,9 @@
|
||||
# Gluetun VPN client
|
||||
|
||||
*Lightweight swiss-knife-like VPN client to tunnel to Cyberghost, FastestVPN,
|
||||
HideMyAss, IPVanish, IVPN, Mullvad, NordVPN, Privado, Private Internet Access, PrivateVPN,
|
||||
ProtonVPN, PureVPN, Surfshark, TorGuard, VPNUnlimited, VyprVPN and Windscribe VPN servers
|
||||
using Go, OpenVPN, iptables, DNS over TLS, ShadowSocks and an HTTP proxy*
|
||||
*Lightweight swiss-knife-like VPN client to tunnel to Cyberghost, ExpressVPN, FastestVPN,
|
||||
HideMyAss, IPVanish, IVPN, Mullvad, NordVPN, Perfect Privacy, Privado, Private Internet Access, PrivateVPN,
|
||||
ProtonVPN, PureVPN, Surfshark, TorGuard, VPNUnlimited, VyprVPN, WeVPN and Windscribe VPN servers
|
||||
using Go, OpenVPN or Wireguard, iptables, DNS over TLS, ShadowSocks and an HTTP proxy*
|
||||
|
||||
**ANNOUNCEMENT**: Wireguard is now supported for all providers supporting it!
|
||||
|
||||
@@ -40,10 +40,14 @@ using Go, OpenVPN, iptables, DNS over TLS, ShadowSocks and an HTTP proxy*
|
||||
|
||||
## Quick links
|
||||
|
||||
- Problem or suggestion?
|
||||
- [Start a discussion](https://github.com/qdm12/gluetun/discussions)
|
||||
- [Create an issue](https://github.com/qdm12/gluetun/issues)
|
||||
- [Setup](#Setup)
|
||||
- [Features](#Features)
|
||||
- Problem?
|
||||
- [Check the Wiki](https://github.com/qdm12/gluetun/wiki)
|
||||
- [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)
|
||||
@@ -53,69 +57,68 @@ using Go, OpenVPN, iptables, DNS over TLS, ShadowSocks and an HTTP proxy*
|
||||
|
||||
[](https://youtu.be/0F6I03LQcI4)
|
||||
|
||||
- [Substack Console interview](https://console.substack.com/p/console-72)
|
||||
|
||||
## Features
|
||||
|
||||
- Based on Alpine 3.14 for a small Docker image of 31MB
|
||||
- Supports: **Cyberghost**, **FastestVPN**, **HideMyAss**, **IPVanish**, **IVPN**, **Mullvad**, **NordVPN**, **Privado**, **Private Internet Access**, **PrivateVPN**, **ProtonVPN**, **PureVPN**, **Surfshark**, **TorGuard**, **VPNUnlimited**, **Vyprvpn**, **Windscribe** servers
|
||||
- Supports OpenVPN
|
||||
- Supports Wireguard for **Mullvad**, **Ivpn** and **Windscribe** (more in progress, see #134)
|
||||
- Based on Alpine 3.14 for a small Docker image of 33MB
|
||||
- Supports: **Cyberghost**, **ExpressVPN**, **FastestVPN**, **HideMyAss**, **IPVanish**, **IVPN**, **Mullvad**, **NordVPN**, **Perfect Privacy**, **Privado**, **Private Internet Access**, **PrivateVPN**, **ProtonVPN**, **PureVPN**, **Surfshark**, **TorGuard**, **VPNUnlimited**, **Vyprvpn**, **WeVPN**, **Windscribe** servers
|
||||
- Supports OpenVPN for all providers listed
|
||||
- Supports Wireguard
|
||||
- For **Mullvad**, **Ivpn** and **Windscribe**
|
||||
- For **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)
|
||||
- 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
|
||||
- Choose the vpn network protocol, `udp` or `tcp`
|
||||
- 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-to-gluetun)
|
||||
- [Connect LAN devices to it](https://github.com/qdm12/gluetun/wiki/Connect-to-gluetun)
|
||||
- [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)
|
||||
- Compatible with amd64, i686 (32 bit), **ARM** 64 bit, ARM 32 bit v6 and v7, and even ppc64le 🎆
|
||||
- VPN server side port forwarding for Private Internet Access and Vyprvpn
|
||||
- [Custom VPN server side port forwarding for Private Internet Access](https://github.com/qdm12/gluetun/wiki/Private-internet-access#vpn-server-port-forwarding)
|
||||
- Possibility of split horizon DNS by selecting multiple DNS over TLS providers
|
||||
- Subprograms all drop root privileges once launched
|
||||
- Subprograms output streams are all merged together
|
||||
- Unbound subprogram drops root privileges once launched
|
||||
- Can work as a Kubernetes sidecar container, thanks @rorph
|
||||
|
||||
## Setup
|
||||
|
||||
1. Ensure your `tun` kernel module is setup:
|
||||
🎉 There are now instructions specific to each VPN provider with examples to help you get started as quickly as possible!
|
||||
|
||||
```sh
|
||||
sudo modprobe tun
|
||||
# or, if you don't have modprobe, with
|
||||
sudo insmod /lib/modules/tun.ko
|
||||
```
|
||||
Go to the [Wiki](https://github.com/qdm12/gluetun/wiki)!
|
||||
|
||||
1. Extra steps:
|
||||
- [For Synology users](https://github.com/qdm12/gluetun/wiki/Synology-setup)
|
||||
- [For 32 bit Operating systems (**Rasberry Pis**)](https://github.com/qdm12/gluetun/wiki/32-bit-setup)
|
||||
1. Launch the container with:
|
||||
[🐛 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+)
|
||||
|
||||
```bash
|
||||
docker run -d --name gluetun --cap-add=NET_ADMIN \
|
||||
-e VPNSP="private internet access" -e REGION="CA Montreal" \
|
||||
-e OPENVPN_USER=js89ds7 -e OPENVPN_PASSWORD=8fd9s239G \
|
||||
-v /yourpath:/gluetun \
|
||||
qmcgaw/gluetun
|
||||
```
|
||||
Here's a docker-compose.yml for the laziest:
|
||||
|
||||
or use [docker-compose.yml](https://github.com/qdm12/gluetun/blob/master/docker-compose.yml) with:
|
||||
|
||||
```bash
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
You should probably check the many [environment variables](https://github.com/qdm12/gluetun/wiki/Environment-variables) available to adapt the container to your needs.
|
||||
|
||||
## Further setup
|
||||
|
||||
The following points are all optional but should give you insights on all the possibilities with this container.
|
||||
|
||||
- [Test your setup](https://github.com/qdm12/gluetun/wiki/Test-your-setup)
|
||||
- [How to connect other containers and devices to Gluetun](https://github.com/qdm12/gluetun/wiki/Connect-to-gluetun)
|
||||
- [How to use Wireguard](https://github.com/qdm12/gluetun/wiki/Wireguard)
|
||||
- [VPN server side port forwarding](https://github.com/qdm12/gluetun/wiki/Port-forwarding)
|
||||
- [HTTP control server](https://github.com/qdm12/gluetun/wiki/HTTP-Control-server) to automate things, restart Openvpn etc.
|
||||
- Update the image with `docker pull qmcgaw/gluetun:latest`. See this [Wiki document](https://github.com/qdm12/gluetun/wiki/Docker-image-tags) for Docker tags available.
|
||||
- Use [Docker secrets](https://github.com/qdm12/gluetun/wiki/Docker-secrets) to read your credentials instead of environment variables
|
||||
```yml
|
||||
version: "3"
|
||||
services:
|
||||
gluetun:
|
||||
image: qmcgaw/gluetun
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
ports:
|
||||
- 8888:8888/tcp # HTTP proxy
|
||||
- 8388:8388/tcp # Shadowsocks
|
||||
- 8388:8388/udp # Shadowsocks
|
||||
volumes:
|
||||
- /yourpath:/gluetun
|
||||
environment:
|
||||
# See https://github.com/qdm12/gluetun/wiki
|
||||
- VPNSP=ivpn
|
||||
- VPN_TYPE=openvpn
|
||||
# OpenVPN:
|
||||
- OPENVPN_USER=
|
||||
- OPENVPN_PASSWORD=
|
||||
# Wireguard:
|
||||
# - WIREGUARD_PRIVATE_KEY=wOEI9rqqbDwnN8/Bpp22sVz48T71vJ4fYmFWujulwUU=
|
||||
# - WIREGUARD_ADDRESS=10.64.222.21/32
|
||||
# Timezone for accurate log times
|
||||
- TZ=
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -11,7 +11,9 @@ import (
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
_ "time/tzdata"
|
||||
|
||||
_ "github.com/breml/rootcerts"
|
||||
"github.com/qdm12/dns/pkg/unbound"
|
||||
"github.com/qdm12/gluetun/internal/alpine"
|
||||
"github.com/qdm12/gluetun/internal/cli"
|
||||
@@ -37,6 +39,9 @@ import (
|
||||
"github.com/qdm12/golibs/logging"
|
||||
"github.com/qdm12/golibs/params"
|
||||
"github.com/qdm12/goshutdown"
|
||||
"github.com/qdm12/goshutdown/goroutine"
|
||||
"github.com/qdm12/goshutdown/group"
|
||||
"github.com/qdm12/goshutdown/order"
|
||||
"github.com/qdm12/gosplash"
|
||||
"github.com/qdm12/updated/pkg/dnscrypto"
|
||||
)
|
||||
@@ -130,11 +135,34 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
||||
return cli.OpenvpnConfig(logger, env)
|
||||
case "update":
|
||||
return cli.Update(ctx, args[2:], logger)
|
||||
case "format-servers":
|
||||
return cli.FormatServers(args[2:])
|
||||
default:
|
||||
return fmt.Errorf("%w: %s", errCommandUnknown, args[1])
|
||||
}
|
||||
}
|
||||
|
||||
announcementExp, err := time.Parse(time.RFC3339, "2021-10-02T00:00:00Z")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
splashSettings := gosplash.Settings{
|
||||
User: "qdm12",
|
||||
Repository: "gluetun",
|
||||
Emails: []string{"quentin.mcgaw@gmail.com"},
|
||||
Version: buildInfo.Version,
|
||||
Commit: buildInfo.Commit,
|
||||
BuildDate: buildInfo.Created,
|
||||
Announcement: "Wireguard is now supported for Mullvad, IVPN and Windscribe!",
|
||||
AnnounceExp: announcementExp,
|
||||
// Sponsor information
|
||||
PaypalUser: "qmcgaw",
|
||||
GithubSponsor: "qdm12",
|
||||
}
|
||||
for _, line := range gosplash.MakeLines(splashSettings) {
|
||||
fmt.Println(line)
|
||||
}
|
||||
|
||||
// TODO run this in a loop or in openvpn to reload from file without restarting
|
||||
storageLogger := logger.NewChild(logging.Settings{Prefix: "storage: "})
|
||||
storage, err := storage.New(storageLogger, constants.ServersData)
|
||||
@@ -165,27 +193,6 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
||||
dnsConf := unbound.NewConfigurator(nil, cmder, dnsCrypto,
|
||||
"/etc/unbound", "/usr/sbin/unbound", cacertsPath)
|
||||
|
||||
announcementExp, err := time.Parse(time.RFC3339, "2021-10-02T00:00:00Z")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
splashSettings := gosplash.Settings{
|
||||
User: "qdm12",
|
||||
Repository: "gluetun",
|
||||
Emails: []string{"quentin.mcgaw@gmail.com"},
|
||||
Version: buildInfo.Version,
|
||||
Commit: buildInfo.Commit,
|
||||
BuildDate: buildInfo.Created,
|
||||
Announcement: "Wireguard is now supported for Mullvad, IVPN and Windscribe!",
|
||||
AnnounceExp: announcementExp,
|
||||
// Sponsor information
|
||||
PaypalUser: "qmcgaw",
|
||||
GithubSponsor: "qdm12",
|
||||
}
|
||||
for _, line := range gosplash.MakeLines(splashSettings) {
|
||||
fmt.Println(line)
|
||||
}
|
||||
|
||||
err = printVersions(ctx, logger, []printVersionElement{
|
||||
{name: "Alpine", getVersion: alpineConf.Version},
|
||||
{name: "OpenVPN 2.4", getVersion: ovpnConf.Version24},
|
||||
@@ -292,14 +299,6 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
||||
}
|
||||
}
|
||||
|
||||
for _, vpnPort := range allSettings.Firewall.VPNInputPorts {
|
||||
vpnIntf := allSettings.VPN.VPNInterface()
|
||||
err = firewallConf.SetAllowedPort(ctx, vpnPort, vpnIntf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, port := range allSettings.Firewall.InputPorts {
|
||||
err = firewallConf.SetAllowedPort(ctx, port, defaultInterface)
|
||||
if err != nil {
|
||||
@@ -308,6 +307,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
||||
} // TODO move inside firewall?
|
||||
|
||||
// Shutdown settings
|
||||
const totalShutdownTimeout = 3 * time.Second
|
||||
const defaultShutdownTimeout = 400 * time.Millisecond
|
||||
defaultShutdownOnSuccess := func(goRoutineName string) {
|
||||
logger.Info(goRoutineName + ": terminated ✔️")
|
||||
@@ -315,34 +315,32 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
||||
defaultShutdownOnFailure := func(goRoutineName string, err error) {
|
||||
logger.Warn(goRoutineName + ": " + err.Error() + " ⚠️")
|
||||
}
|
||||
defaultGoRoutineSettings := goshutdown.GoRoutineSettings{Timeout: defaultShutdownTimeout}
|
||||
defaultGroupSettings := goshutdown.GroupSettings{
|
||||
Timeout: defaultShutdownTimeout,
|
||||
OnSuccess: defaultShutdownOnSuccess,
|
||||
}
|
||||
defaultGroupOptions := []group.Option{
|
||||
group.OptionTimeout(defaultShutdownTimeout),
|
||||
group.OptionOnSuccess(defaultShutdownOnSuccess)}
|
||||
|
||||
controlGroupHandler := goshutdown.NewGroupHandler("control", defaultGroupSettings)
|
||||
tickersGroupHandler := goshutdown.NewGroupHandler("tickers", defaultGroupSettings)
|
||||
otherGroupHandler := goshutdown.NewGroupHandler("other", defaultGroupSettings)
|
||||
controlGroupHandler := goshutdown.NewGroupHandler("control", defaultGroupOptions...)
|
||||
tickersGroupHandler := goshutdown.NewGroupHandler("tickers", defaultGroupOptions...)
|
||||
otherGroupHandler := goshutdown.NewGroupHandler("other", defaultGroupOptions...)
|
||||
|
||||
portForwardLogger := logger.NewChild(logging.Settings{Prefix: "port forwarding: "})
|
||||
portForwardLooper := portforward.NewLoop(allSettings.VPN.Provider.PortForwarding,
|
||||
httpClient, firewallConf, portForwardLogger)
|
||||
portForwardHandler, portForwardCtx, portForwardDone := goshutdown.NewGoRoutineHandler(
|
||||
"port forwarding", goshutdown.GoRoutineSettings{Timeout: time.Second})
|
||||
"port forwarding", goroutine.OptionTimeout(time.Second))
|
||||
go portForwardLooper.Run(portForwardCtx, portForwardDone)
|
||||
|
||||
unboundLogger := logger.NewChild(logging.Settings{Prefix: "dns over tls: "})
|
||||
unboundLooper := dns.NewLoop(dnsConf, allSettings.DNS, httpClient,
|
||||
unboundLogger)
|
||||
dnsHandler, dnsCtx, dnsDone := goshutdown.NewGoRoutineHandler(
|
||||
"unbound", defaultGoRoutineSettings)
|
||||
"unbound", goroutine.OptionTimeout(defaultShutdownTimeout))
|
||||
// wait for unboundLooper.Restart or its ticker launched with RunRestartTicker
|
||||
go unboundLooper.Run(dnsCtx, dnsDone)
|
||||
otherGroupHandler.Add(dnsHandler)
|
||||
|
||||
dnsTickerHandler, dnsTickerCtx, dnsTickerDone := goshutdown.NewGoRoutineHandler(
|
||||
"dns ticker", defaultGoRoutineSettings)
|
||||
"dns ticker", goroutine.OptionTimeout(defaultShutdownTimeout))
|
||||
go unboundLooper.RunRestartTicker(dnsTickerCtx, dnsTickerDone)
|
||||
controlGroupHandler.Add(dnsTickerHandler)
|
||||
|
||||
@@ -350,35 +348,35 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
||||
logger.NewChild(logging.Settings{Prefix: "ip getter: "}),
|
||||
allSettings.PublicIP, puid, pgid)
|
||||
pubIPHandler, pubIPCtx, pubIPDone := goshutdown.NewGoRoutineHandler(
|
||||
"public IP", defaultGoRoutineSettings)
|
||||
"public IP", goroutine.OptionTimeout(defaultShutdownTimeout))
|
||||
go publicIPLooper.Run(pubIPCtx, pubIPDone)
|
||||
otherGroupHandler.Add(pubIPHandler)
|
||||
|
||||
pubIPTickerHandler, pubIPTickerCtx, pubIPTickerDone := goshutdown.NewGoRoutineHandler(
|
||||
"public IP", defaultGoRoutineSettings)
|
||||
"public IP", goroutine.OptionTimeout(defaultShutdownTimeout))
|
||||
go publicIPLooper.RunRestartTicker(pubIPTickerCtx, pubIPTickerDone)
|
||||
tickersGroupHandler.Add(pubIPTickerHandler)
|
||||
|
||||
vpnLogger := logger.NewChild(logging.Settings{Prefix: "vpn: "})
|
||||
vpnLooper := vpn.NewLoop(allSettings.VPN,
|
||||
vpnLooper := vpn.NewLoop(allSettings.VPN, allSettings.Firewall.VPNInputPorts,
|
||||
allServers, ovpnConf, netLinker, firewallConf, routingConf, portForwardLooper,
|
||||
cmder, publicIPLooper, unboundLooper, vpnLogger, httpClient,
|
||||
buildInfo, allSettings.VersionInformation)
|
||||
vpnHandler, vpnCtx, vpnDone := goshutdown.NewGoRoutineHandler(
|
||||
"vpn", goshutdown.GoRoutineSettings{Timeout: time.Second})
|
||||
"vpn", goroutine.OptionTimeout(time.Second))
|
||||
go vpnLooper.Run(vpnCtx, vpnDone)
|
||||
|
||||
updaterLooper := updater.NewLooper(allSettings.Updater,
|
||||
allServers, storage, vpnLooper.SetServers, httpClient,
|
||||
logger.NewChild(logging.Settings{Prefix: "updater: "}))
|
||||
updaterHandler, updaterCtx, updaterDone := goshutdown.NewGoRoutineHandler(
|
||||
"updater", defaultGoRoutineSettings)
|
||||
"updater", goroutine.OptionTimeout(defaultShutdownTimeout))
|
||||
// wait for updaterLooper.Restart() or its ticket launched with RunRestartTicker
|
||||
go updaterLooper.Run(updaterCtx, updaterDone)
|
||||
tickersGroupHandler.Add(updaterHandler)
|
||||
|
||||
updaterTickerHandler, updaterTickerCtx, updaterTickerDone := goshutdown.NewGoRoutineHandler(
|
||||
"updater ticker", defaultGoRoutineSettings)
|
||||
"updater ticker", goroutine.OptionTimeout(defaultShutdownTimeout))
|
||||
go updaterLooper.RunRestartTicker(updaterTickerCtx, updaterTickerDone)
|
||||
controlGroupHandler.Add(updaterTickerHandler)
|
||||
|
||||
@@ -386,21 +384,21 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
||||
logger.NewChild(logging.Settings{Prefix: "http proxy: "}),
|
||||
allSettings.HTTPProxy)
|
||||
httpProxyHandler, httpProxyCtx, httpProxyDone := goshutdown.NewGoRoutineHandler(
|
||||
"http proxy", defaultGoRoutineSettings)
|
||||
"http proxy", goroutine.OptionTimeout(defaultShutdownTimeout))
|
||||
go httpProxyLooper.Run(httpProxyCtx, httpProxyDone)
|
||||
otherGroupHandler.Add(httpProxyHandler)
|
||||
|
||||
shadowsocksLooper := shadowsocks.NewLooper(allSettings.ShadowSocks,
|
||||
logger.NewChild(logging.Settings{Prefix: "shadowsocks: "}))
|
||||
shadowsocksHandler, shadowsocksCtx, shadowsocksDone := goshutdown.NewGoRoutineHandler(
|
||||
"shadowsocks proxy", defaultGoRoutineSettings)
|
||||
"shadowsocks proxy", goroutine.OptionTimeout(defaultShutdownTimeout))
|
||||
go shadowsocksLooper.Run(shadowsocksCtx, shadowsocksDone)
|
||||
otherGroupHandler.Add(shadowsocksHandler)
|
||||
|
||||
controlServerAddress := ":" + strconv.Itoa(int(allSettings.ControlServer.Port))
|
||||
controlServerLogging := allSettings.ControlServer.Log
|
||||
httpServerHandler, httpServerCtx, httpServerDone := goshutdown.NewGoRoutineHandler(
|
||||
"http server", defaultGoRoutineSettings)
|
||||
"http server", goroutine.OptionTimeout(defaultShutdownTimeout))
|
||||
httpServer := server.New(httpServerCtx, controlServerAddress, controlServerLogging,
|
||||
logger.NewChild(logging.Settings{Prefix: "http server: "}),
|
||||
buildInfo, vpnLooper, portForwardLooper, unboundLooper, updaterLooper, publicIPLooper)
|
||||
@@ -410,16 +408,13 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
||||
healthLogger := logger.NewChild(logging.Settings{Prefix: "healthcheck: "})
|
||||
healthcheckServer := healthcheck.NewServer(allSettings.Health, healthLogger, vpnLooper)
|
||||
healthServerHandler, healthServerCtx, healthServerDone := goshutdown.NewGoRoutineHandler(
|
||||
"HTTP health server", defaultGoRoutineSettings)
|
||||
"HTTP health server", goroutine.OptionTimeout(defaultShutdownTimeout))
|
||||
go healthcheckServer.Run(healthServerCtx, healthServerDone)
|
||||
|
||||
const orderShutdownTimeout = 3 * time.Second
|
||||
orderSettings := goshutdown.OrderSettings{
|
||||
Timeout: orderShutdownTimeout,
|
||||
OnFailure: defaultShutdownOnFailure,
|
||||
OnSuccess: defaultShutdownOnSuccess,
|
||||
}
|
||||
orderHandler := goshutdown.NewOrder("gluetun", orderSettings)
|
||||
orderHandler := goshutdown.NewOrderHandler("gluetun",
|
||||
order.OptionTimeout(totalShutdownTimeout),
|
||||
order.OptionOnSuccess(defaultShutdownOnSuccess),
|
||||
order.OptionOnFailure(defaultShutdownOnFailure))
|
||||
orderHandler.Append(controlGroupHandler, tickersGroupHandler, healthServerHandler,
|
||||
vpnHandler, portForwardHandler, otherGroupHandler)
|
||||
|
||||
@@ -437,7 +432,11 @@ type printVersionElement struct {
|
||||
getVersion func(ctx context.Context) (version string, err error)
|
||||
}
|
||||
|
||||
func printVersions(ctx context.Context, logger logging.Logger,
|
||||
type infoer interface {
|
||||
Info(s string)
|
||||
}
|
||||
|
||||
func printVersions(ctx context.Context, logger infoer,
|
||||
elements []printVersionElement) (err error) {
|
||||
const timeout = 5 * time.Second
|
||||
ctx, cancel := context.WithTimeout(ctx, timeout)
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
version: "3.7"
|
||||
services:
|
||||
gluetun:
|
||||
image: qmcgaw/gluetun
|
||||
container_name: gluetun
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
network_mode: bridge
|
||||
ports:
|
||||
- 8888:8888/tcp # HTTP proxy
|
||||
- 8388:8388/tcp # Shadowsocks
|
||||
- 8388:8388/udp # Shadowsocks
|
||||
- 8000:8000/tcp # Built-in HTTP control server
|
||||
# command:
|
||||
volumes:
|
||||
- /yourpath:/gluetun
|
||||
environment:
|
||||
# More variables are available, see the Wiki table
|
||||
- OPENVPN_USER=
|
||||
- OPENVPN_PASSWORD=
|
||||
- VPNSP=private internet access
|
||||
- VPN_TYPE=openvpn
|
||||
# Timezone for accurate logs times
|
||||
- TZ=
|
||||
restart: always
|
||||
12
go.mod
12
go.mod
@@ -3,11 +3,13 @@ module github.com/qdm12/gluetun
|
||||
go 1.17
|
||||
|
||||
require (
|
||||
github.com/fatih/color v1.12.0
|
||||
github.com/breml/rootcerts v0.2.0
|
||||
github.com/fatih/color v1.13.0
|
||||
github.com/go-ping/ping v0.0.0-20210911151512-381826476871
|
||||
github.com/golang/mock v1.6.0
|
||||
github.com/qdm12/dns v1.11.0
|
||||
github.com/qdm12/golibs v0.0.0-20210822203818-5c568b0777b6
|
||||
github.com/qdm12/goshutdown v0.1.0
|
||||
github.com/qdm12/goshutdown v0.3.0
|
||||
github.com/qdm12/gosplash v0.1.0
|
||||
github.com/qdm12/ss-server v0.3.0
|
||||
github.com/qdm12/updated v0.0.0-20210603204757-205acfe6937e
|
||||
@@ -22,9 +24,10 @@ require (
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/google/go-cmp v0.5.5 // indirect
|
||||
github.com/google/uuid v1.2.0 // indirect
|
||||
github.com/josharian/native v0.0.0-20200817173448-b6b71def0850 // indirect
|
||||
github.com/mattn/go-colorable v0.1.8 // indirect
|
||||
github.com/mattn/go-isatty v0.0.12 // indirect
|
||||
github.com/mattn/go-colorable v0.1.9 // indirect
|
||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||
github.com/mdlayher/genetlink v1.0.0 // indirect
|
||||
github.com/mdlayher/netlink v1.4.0 // indirect
|
||||
github.com/miekg/dns v1.1.40 // indirect
|
||||
@@ -36,5 +39,6 @@ require (
|
||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222180813-1025295fd063 // indirect
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect
|
||||
golang.org/x/net v0.0.0-20210504132125-bbd867fde50d // indirect
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
|
||||
)
|
||||
|
||||
21
go.sum
21
go.sum
@@ -4,6 +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.0 h1:bBIgVe8bS0Ec+orgWpZ/GRYt3a0O8yoW+g2kSBY2aLE=
|
||||
github.com/breml/rootcerts v0.2.0/go.mod h1:24FDtzYMpqIeYC7QzaE8VPRQaFZU5TIUDlyk8qwjD88=
|
||||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
@@ -11,8 +13,9 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
||||
github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
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 h1:mRhaKNwANqRgUBGKmnI5ZxEk7QXmjQeCcuYFMX2bfcc=
|
||||
github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
||||
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
|
||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||
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=
|
||||
@@ -29,6 +32,8 @@ github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsd
|
||||
github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=
|
||||
github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
|
||||
github.com/go-openapi/validate v0.17.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4=
|
||||
github.com/go-ping/ping v0.0.0-20210911151512-381826476871 h1:wtjTfjwAR/BYYMJ+QOLI/3J/qGEI0fgrkZvgsEWK2/Q=
|
||||
github.com/go-ping/ping v0.0.0-20210911151512-381826476871/go.mod h1:xIFjORFzTxqIV/tDVGO4eDy/bLuSyawEeojSm3GfRGk=
|
||||
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
|
||||
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||
@@ -42,6 +47,8 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
|
||||
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gotify/go-api-client/v2 v2.0.4/go.mod h1:VKiah/UK20bXsr0JObE1eBVLW44zbBouzjuri9iwjFU=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
@@ -64,10 +71,12 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kyokomi/emoji v2.2.4+incompatible/go.mod h1:mZ6aGCD7yk8j6QY6KICwnZ2pxoszVseX1DNoGtU2tBA=
|
||||
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
|
||||
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||
github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U=
|
||||
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mdlayher/ethtool v0.0.0-20210210192532-2b88debcdd43 h1:WgyLFv10Ov49JAQI/ZLUkCZ7VJS3r74hwFIGXJsgZlY=
|
||||
github.com/mdlayher/ethtool v0.0.0-20210210192532-2b88debcdd43/go.mod h1:+t7E0lkKfbBsebllff1xdTmyJt8lH37niI6kwFk9OTo=
|
||||
github.com/mdlayher/genetlink v1.0.0 h1:OoHN1OdyEIkScEmRgxLEe2M9U8ClMytqA5niynLtfj0=
|
||||
@@ -102,8 +111,8 @@ 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/goshutdown v0.1.0 h1:lmwnygdXtnr2pa6VqfR/bm8077/BnBef1+7CP96B7Sw=
|
||||
github.com/qdm12/goshutdown v0.1.0/go.mod h1:/LP3MWLqI+wGH/ijfaUG+RHzBbKXIiVKnrg5vXOCf6Q=
|
||||
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/ss-server v0.3.0 h1:BfKv4OU6dYb2KcDMYpTc7LIuO2jB73g3JCzy988GrLI=
|
||||
@@ -163,6 +172,7 @@ golang.org/x/net v0.0.0-20201216054612-986b41b23924/go.mod h1:m0MpNAwzfU5UDzcl9v
|
||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210504132125-bbd867fde50d h1:nTDGCTeAu2LhcsHTRzjyIUbZHCJ4QePArsm27Hka0UM=
|
||||
golang.org/x/net v0.0.0-20210504132125-bbd867fde50d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
@@ -195,6 +205,7 @@ golang.org/x/sys v0.0.0-20210123111255-9b0068b26619/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210216163648-f7da38b97c65/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210309040221-94ec62e08169/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
||||
@@ -8,6 +8,7 @@ type CLIer interface {
|
||||
HealthChecker
|
||||
OpenvpnConfigMaker
|
||||
Updater
|
||||
ServersFormatter
|
||||
}
|
||||
|
||||
type CLI struct {
|
||||
@@ -16,6 +17,6 @@ type CLI struct {
|
||||
|
||||
func New() *CLI {
|
||||
return &CLI{
|
||||
repoServersPath: "./internal/constants/servers.json",
|
||||
repoServersPath: "./internal/storage/servers.json",
|
||||
}
|
||||
}
|
||||
|
||||
133
internal/cli/formatservers.go
Normal file
133
internal/cli/formatservers.go
Normal file
@@ -0,0 +1,133 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/storage"
|
||||
)
|
||||
|
||||
type ServersFormatter interface {
|
||||
FormatServers(args []string) error
|
||||
}
|
||||
|
||||
var (
|
||||
ErrFormatNotRecognized = errors.New("format is not recognized")
|
||||
ErrProviderUnspecified = errors.New("VPN provider to format was not specified")
|
||||
ErrOpenOutputFile = errors.New("cannot open output file")
|
||||
ErrWriteOutput = errors.New("cannot write to output file")
|
||||
ErrCloseOutputFile = errors.New("cannot close output file")
|
||||
)
|
||||
|
||||
func (c *CLI) FormatServers(args []string) error {
|
||||
var format, output string
|
||||
var cyberghost, expressvpn, fastestvpn, hideMyAss, ipvanish, ivpn, mullvad,
|
||||
nordvpn, perfectPrivacy, pia, privado, privatevpn, protonvpn, purevpn, surfshark,
|
||||
torguard, vpnUnlimited, vyprvpn, wevpn, windscribe bool
|
||||
flagSet := flag.NewFlagSet("markdown", flag.ExitOnError)
|
||||
flagSet.StringVar(&format, "format", "markdown", "Format to use which can be: 'markdown'")
|
||||
flagSet.StringVar(&output, "output", "/dev/stdout", "Output file to write the formatted data to")
|
||||
flagSet.BoolVar(&cyberghost, "cyberghost", false, "Format Cyberghost servers")
|
||||
flagSet.BoolVar(&expressvpn, "expressvpn", false, "Format ExpressVPN servers")
|
||||
flagSet.BoolVar(&fastestvpn, "fastestvpn", false, "Format FastestVPN servers")
|
||||
flagSet.BoolVar(&hideMyAss, "hidemyass", false, "Format HideMyAss servers")
|
||||
flagSet.BoolVar(&ipvanish, "ipvanish", false, "Format IpVanish servers")
|
||||
flagSet.BoolVar(&ivpn, "ivpn", false, "Format IVPN servers")
|
||||
flagSet.BoolVar(&mullvad, "mullvad", false, "Format Mullvad servers")
|
||||
flagSet.BoolVar(&nordvpn, "nordvpn", false, "Format Nordvpn servers")
|
||||
flagSet.BoolVar(&perfectPrivacy, "perfectprivacy", false, "Format Perfect Privacy servers")
|
||||
flagSet.BoolVar(&pia, "pia", false, "Format Private Internet Access servers")
|
||||
flagSet.BoolVar(&privado, "privado", false, "Format Privado servers")
|
||||
flagSet.BoolVar(&privatevpn, "privatevpn", false, "Format Private VPN servers")
|
||||
flagSet.BoolVar(&protonvpn, "protonvpn", false, "Format Protonvpn servers")
|
||||
flagSet.BoolVar(&purevpn, "purevpn", false, "Format Purevpn servers")
|
||||
flagSet.BoolVar(&surfshark, "surfshark", false, "Format Surfshark servers")
|
||||
flagSet.BoolVar(&torguard, "torguard", false, "Format Torguard servers")
|
||||
flagSet.BoolVar(&vpnUnlimited, "vpnunlimited", false, "Format VPN Unlimited servers")
|
||||
flagSet.BoolVar(&vyprvpn, "vyprvpn", false, "Format Vyprvpn servers")
|
||||
flagSet.BoolVar(&wevpn, "wevpn", false, "Format WeVPN servers")
|
||||
flagSet.BoolVar(&windscribe, "windscribe", false, "Format Windscribe servers")
|
||||
if err := flagSet.Parse(args); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if format != "markdown" {
|
||||
return fmt.Errorf("%w: %s", ErrFormatNotRecognized, format)
|
||||
}
|
||||
|
||||
logger := newNoopLogger()
|
||||
storage, err := storage.New(logger, constants.ServersData)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %s", ErrNewStorage, err)
|
||||
}
|
||||
currentServers := storage.GetServers()
|
||||
|
||||
var formatted string
|
||||
switch {
|
||||
case cyberghost:
|
||||
formatted = currentServers.Cyberghost.ToMarkdown()
|
||||
case expressvpn:
|
||||
formatted = currentServers.Expressvpn.ToMarkdown()
|
||||
case fastestvpn:
|
||||
formatted = currentServers.Fastestvpn.ToMarkdown()
|
||||
case hideMyAss:
|
||||
formatted = currentServers.HideMyAss.ToMarkdown()
|
||||
case ipvanish:
|
||||
formatted = currentServers.Ipvanish.ToMarkdown()
|
||||
case ivpn:
|
||||
formatted = currentServers.Ivpn.ToMarkdown()
|
||||
case mullvad:
|
||||
formatted = currentServers.Mullvad.ToMarkdown()
|
||||
case nordvpn:
|
||||
formatted = currentServers.Nordvpn.ToMarkdown()
|
||||
case perfectPrivacy:
|
||||
formatted = currentServers.Perfectprivacy.ToMarkdown()
|
||||
case pia:
|
||||
formatted = currentServers.Pia.ToMarkdown()
|
||||
case privado:
|
||||
formatted = currentServers.Privado.ToMarkdown()
|
||||
case privatevpn:
|
||||
formatted = currentServers.Privatevpn.ToMarkdown()
|
||||
case protonvpn:
|
||||
formatted = currentServers.Protonvpn.ToMarkdown()
|
||||
case purevpn:
|
||||
formatted = currentServers.Purevpn.ToMarkdown()
|
||||
case surfshark:
|
||||
formatted = currentServers.Surfshark.ToMarkdown()
|
||||
case torguard:
|
||||
formatted = currentServers.Torguard.ToMarkdown()
|
||||
case vpnUnlimited:
|
||||
formatted = currentServers.VPNUnlimited.ToMarkdown()
|
||||
case vyprvpn:
|
||||
formatted = currentServers.Vyprvpn.ToMarkdown()
|
||||
case wevpn:
|
||||
formatted = currentServers.Wevpn.ToMarkdown()
|
||||
case windscribe:
|
||||
formatted = currentServers.Windscribe.ToMarkdown()
|
||||
default:
|
||||
return ErrProviderUnspecified
|
||||
}
|
||||
|
||||
output = filepath.Clean(output)
|
||||
file, err := os.OpenFile(output, os.O_TRUNC|os.O_WRONLY|os.O_CREATE, 0644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %s", ErrOpenOutputFile, err)
|
||||
}
|
||||
|
||||
_, err = fmt.Fprint(file, formatted)
|
||||
if err != nil {
|
||||
_ = file.Close()
|
||||
return fmt.Errorf("%w: %s", ErrWriteOutput, err)
|
||||
}
|
||||
|
||||
err = file.Close()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %s", ErrCloseOutputFile, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -8,19 +8,18 @@ import (
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration"
|
||||
"github.com/qdm12/gluetun/internal/healthcheck"
|
||||
"github.com/qdm12/golibs/logging"
|
||||
"github.com/qdm12/golibs/params"
|
||||
)
|
||||
|
||||
type HealthChecker interface {
|
||||
HealthCheck(ctx context.Context, env params.Interface, logger logging.Logger) error
|
||||
HealthCheck(ctx context.Context, env params.Interface, warner configuration.Warner) error
|
||||
}
|
||||
|
||||
func (c *CLI) HealthCheck(ctx context.Context, env params.Interface,
|
||||
logger logging.Logger) error {
|
||||
warner configuration.Warner) error {
|
||||
// Extract the health server port from the configuration.
|
||||
config := configuration.Health{}
|
||||
err := config.Read(env, logger)
|
||||
err := config.Read(env, warner)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
16
internal/cli/nooplogger.go
Normal file
16
internal/cli/nooplogger.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package cli
|
||||
|
||||
import "github.com/qdm12/golibs/logging"
|
||||
|
||||
type noopLogger struct{}
|
||||
|
||||
func newNoopLogger() *noopLogger {
|
||||
return new(noopLogger)
|
||||
}
|
||||
|
||||
func (l *noopLogger) Debug(s string) {}
|
||||
func (l *noopLogger) Info(s string) {}
|
||||
func (l *noopLogger) Warn(s string) {}
|
||||
func (l *noopLogger) Error(s string) {}
|
||||
func (l *noopLogger) PatchLevel(level logging.Level) {}
|
||||
func (l *noopLogger) PatchPrefix(prefix string) {}
|
||||
@@ -9,15 +9,19 @@ import (
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/provider"
|
||||
"github.com/qdm12/gluetun/internal/storage"
|
||||
"github.com/qdm12/golibs/logging"
|
||||
"github.com/qdm12/golibs/params"
|
||||
)
|
||||
|
||||
type OpenvpnConfigMaker interface {
|
||||
OpenvpnConfig(logger logging.Logger, env params.Interface) error
|
||||
OpenvpnConfig(logger OpenvpnConfigLogger, env params.Interface) error
|
||||
}
|
||||
|
||||
func (c *CLI) OpenvpnConfig(logger logging.Logger, env params.Interface) error {
|
||||
type OpenvpnConfigLogger interface {
|
||||
Info(s string)
|
||||
Warn(s string)
|
||||
}
|
||||
|
||||
func (c *CLI) OpenvpnConfig(logger OpenvpnConfigLogger, env params.Interface) error {
|
||||
storage, err := storage.New(logger, constants.ServersData)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -34,7 +38,11 @@ func (c *CLI) OpenvpnConfig(logger logging.Logger, env params.Interface) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
lines := providerConf.BuildConf(connection, allSettings.VPN.OpenVPN)
|
||||
lines, err := providerConf.BuildConf(connection, allSettings.VPN.OpenVPN)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println(strings.Join(lines, "\n"))
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -15,36 +15,43 @@ import (
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/qdm12/gluetun/internal/storage"
|
||||
"github.com/qdm12/gluetun/internal/updater"
|
||||
"github.com/qdm12/golibs/logging"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrModeUnspecified = errors.New("at least one of -enduser or -maintainers must be specified")
|
||||
ErrModeUnspecified = errors.New("at least one of -enduser or -maintainer must be specified")
|
||||
ErrNewStorage = errors.New("cannot create storage")
|
||||
ErrUpdateServerInformation = errors.New("cannot update server information")
|
||||
ErrWriteToFile = errors.New("cannot write updated information to file")
|
||||
)
|
||||
|
||||
type Updater interface {
|
||||
Update(ctx context.Context, args []string, logger logging.Logger) error
|
||||
Update(ctx context.Context, args []string, logger UpdaterLogger) error
|
||||
}
|
||||
|
||||
func (c *CLI) Update(ctx context.Context, args []string, logger logging.Logger) error {
|
||||
type UpdaterLogger interface {
|
||||
Info(s string)
|
||||
Warn(s string)
|
||||
Error(s string)
|
||||
}
|
||||
|
||||
func (c *CLI) Update(ctx context.Context, args []string, logger UpdaterLogger) error {
|
||||
options := configuration.Updater{CLI: true}
|
||||
var endUserMode, maintainerMode, updateAll bool
|
||||
flagSet := flag.NewFlagSet("update", flag.ExitOnError)
|
||||
flagSet.BoolVar(&endUserMode, "enduser", false, "Write results to /gluetun/servers.json (for end users)")
|
||||
flagSet.BoolVar(&maintainerMode, "maintainer", false,
|
||||
"Write results to ./internal/constants/servers.json to modify the program (for maintainers)")
|
||||
"Write results to ./internal/storage/servers.json to modify the program (for maintainers)")
|
||||
flagSet.StringVar(&options.DNSAddress, "dns", "8.8.8.8", "DNS resolver address to use")
|
||||
flagSet.BoolVar(&updateAll, "all", false, "Update servers for all VPN providers")
|
||||
flagSet.BoolVar(&options.Cyberghost, "cyberghost", false, "Update Cyberghost servers")
|
||||
flagSet.BoolVar(&options.Expressvpn, "expressvpn", false, "Update ExpressVPN servers")
|
||||
flagSet.BoolVar(&options.Fastestvpn, "fastestvpn", false, "Update FastestVPN servers")
|
||||
flagSet.BoolVar(&options.HideMyAss, "hidemyass", false, "Update HideMyAss servers")
|
||||
flagSet.BoolVar(&options.Ipvanish, "ipvanish", false, "Update IpVanish servers")
|
||||
flagSet.BoolVar(&options.Ivpn, "ivpn", false, "Update IVPN servers")
|
||||
flagSet.BoolVar(&options.Mullvad, "mullvad", false, "Update Mullvad servers")
|
||||
flagSet.BoolVar(&options.Nordvpn, "nordvpn", false, "Update Nordvpn servers")
|
||||
flagSet.BoolVar(&options.Perfectprivacy, "perfectprivacy", false, "Update Perfect Privacy servers")
|
||||
flagSet.BoolVar(&options.PIA, "pia", false, "Update Private Internet Access post-summer 2020 servers")
|
||||
flagSet.BoolVar(&options.Privado, "privado", false, "Update Privado servers")
|
||||
flagSet.BoolVar(&options.Privatevpn, "privatevpn", false, "Update Private VPN servers")
|
||||
@@ -54,6 +61,7 @@ func (c *CLI) Update(ctx context.Context, args []string, logger logging.Logger)
|
||||
flagSet.BoolVar(&options.Torguard, "torguard", false, "Update Torguard servers")
|
||||
flagSet.BoolVar(&options.VPNUnlimited, "vpnunlimited", false, "Update VPN Unlimited servers")
|
||||
flagSet.BoolVar(&options.Vyprvpn, "vyprvpn", false, "Update Vyprvpn servers")
|
||||
flagSet.BoolVar(&options.Wevpn, "wevpn", false, "Update WeVPN servers")
|
||||
flagSet.BoolVar(&options.Windscribe, "windscribe", false, "Update Windscribe servers")
|
||||
if err := flagSet.Parse(args); err != nil {
|
||||
return err
|
||||
|
||||
95
internal/configuration/custom.go
Normal file
95
internal/configuration/custom.go
Normal file
@@ -0,0 +1,95 @@
|
||||
package configuration
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/golibs/params"
|
||||
)
|
||||
|
||||
var (
|
||||
errCustomNotSupported = errors.New("custom provider is not supported")
|
||||
errCustomExtractFromFile = errors.New("cannot extract configuration from file")
|
||||
)
|
||||
|
||||
func (settings *Provider) readCustom(r reader, vpnType string) (err error) {
|
||||
settings.Name = constants.Custom
|
||||
|
||||
switch vpnType {
|
||||
case constants.OpenVPN:
|
||||
return settings.ServerSelection.OpenVPN.readCustom(r)
|
||||
case constants.Wireguard:
|
||||
return settings.ServerSelection.Wireguard.readCustom(r)
|
||||
default:
|
||||
return fmt.Errorf("%w: for VPN type %s", errCustomNotSupported, vpnType)
|
||||
}
|
||||
}
|
||||
|
||||
func (settings *OpenVPNSelection) readCustom(r reader) (err error) {
|
||||
configFile, err := r.env.Get("OPENVPN_CUSTOM_CONFIG", params.CaseSensitiveValue(), params.Compulsory())
|
||||
if err != nil {
|
||||
return fmt.Errorf("environment variable OPENVPN_CUSTOM_CONFIG: %w", err)
|
||||
}
|
||||
settings.ConfFile = configFile
|
||||
|
||||
// For display and consistency purposes only,
|
||||
// these values are not actually used since the file is re-read
|
||||
// before each OpenVPN start.
|
||||
_, connection, err := r.ovpnExt.Data(configFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %s", errCustomExtractFromFile, err)
|
||||
}
|
||||
settings.TCP = connection.Protocol == constants.TCP
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (settings *OpenVPN) readCustom(r reader) (err error) {
|
||||
settings.ConfFile, err = r.env.Path("OPENVPN_CUSTOM_CONFIG",
|
||||
params.Compulsory(), params.CaseSensitiveValue())
|
||||
if err != nil {
|
||||
return fmt.Errorf("environment variable OPENVPN_CUSTOM_CONFIG: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (settings *WireguardSelection) readCustom(r reader) (err error) {
|
||||
settings.PublicKey, err = r.env.Get("WIREGUARD_PUBLIC_KEY",
|
||||
params.CaseSensitiveValue(), params.Compulsory())
|
||||
if err != nil {
|
||||
return fmt.Errorf("environment variable WIREGUARD_PUBLIC_KEY: %w", err)
|
||||
}
|
||||
|
||||
settings.EndpointIP, err = readWireguardEndpointIP(r.env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.EndpointPort, err = r.env.Port("WIREGUARD_ENDPOINT_PORT", params.Compulsory(),
|
||||
params.RetroKeys([]string{"WIREGUARD_PORT"}, r.onRetroActive))
|
||||
if err != nil {
|
||||
return fmt.Errorf("environment variable WIREGUARD_ENDPOINT_PORT: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// readWireguardEndpointIP reads and parses the server endpoint IP
|
||||
// address from the environment variable WIREGUARD_ENDPOINT_IP.
|
||||
func readWireguardEndpointIP(env params.Interface) (endpointIP net.IP, err error) {
|
||||
s, err := env.Get("WIREGUARD_ENDPOINT_IP", params.Compulsory())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("environment variable WIREGUARD_ENDPOINT_IP: %w", err)
|
||||
}
|
||||
|
||||
endpointIP = net.ParseIP(s)
|
||||
if endpointIP == nil {
|
||||
return nil, fmt.Errorf("environment variable WIREGUARD_ENDPOINT_IP: %w: %s",
|
||||
ErrInvalidIP, s)
|
||||
}
|
||||
|
||||
return endpointIP, nil
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/golibs/params"
|
||||
)
|
||||
|
||||
func (settings *Provider) readCyberghost(r reader) (err error) {
|
||||
@@ -15,15 +16,11 @@ func (settings *Provider) readCyberghost(r reader) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.ServerSelection.Groups, err = r.env.CSVInside("CYBERGHOST_GROUP",
|
||||
constants.CyberghostGroupChoices(servers))
|
||||
settings.ServerSelection.Countries, err = r.env.CSVInside("COUNTRY",
|
||||
constants.CyberghostCountryChoices(servers),
|
||||
params.RetroKeys([]string{"REGION"}, r.onRetroActive))
|
||||
if err != nil {
|
||||
return fmt.Errorf("environment variable CYBERGHOST_GROUP: %w", err)
|
||||
}
|
||||
|
||||
settings.ServerSelection.Regions, err = r.env.CSVInside("REGION", constants.CyberghostRegionChoices(servers))
|
||||
if err != nil {
|
||||
return fmt.Errorf("environment variable REGION: %w", err)
|
||||
return fmt.Errorf("environment variable COUNTRY: %w", err)
|
||||
}
|
||||
|
||||
settings.ServerSelection.Hostnames, err = r.env.CSVInside("SERVER_HOSTNAME",
|
||||
@@ -32,18 +29,18 @@ func (settings *Provider) readCyberghost(r reader) (err error) {
|
||||
return fmt.Errorf("environment variable SERVER_HOSTNAME: %w", err)
|
||||
}
|
||||
|
||||
return settings.ServerSelection.OpenVPN.readProtocolAndPort(r.env)
|
||||
return settings.ServerSelection.OpenVPN.readProtocolAndPort(r)
|
||||
}
|
||||
|
||||
func (settings *OpenVPN) readCyberghost(r reader) (err error) {
|
||||
settings.ClientKey, err = readClientKey(r)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("%w: %s", errClientKey, err)
|
||||
}
|
||||
|
||||
settings.ClientCrt, err = readClientCertificate(r)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("%w: %s", errClientCert, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
40
internal/configuration/expressvpn.go
Normal file
40
internal/configuration/expressvpn.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package configuration
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
)
|
||||
|
||||
func (settings *Provider) readExpressvpn(r reader) (err error) {
|
||||
settings.Name = constants.Expressvpn
|
||||
servers := r.servers.GetExpressvpn()
|
||||
|
||||
settings.ServerSelection.TargetIP, err = readTargetIP(r.env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.ServerSelection.Hostnames, err = r.env.CSVInside("SERVER_HOSTNAME",
|
||||
constants.ExpressvpnHostnameChoices(servers))
|
||||
if err != nil {
|
||||
return fmt.Errorf("environment variable SERVER_HOSTNAME: %w", err)
|
||||
}
|
||||
|
||||
settings.ServerSelection.Countries, err = r.env.CSVInside("COUNTRY", constants.ExpressvpnCountriesChoices(servers))
|
||||
if err != nil {
|
||||
return fmt.Errorf("environment variable COUNTRY: %w", err)
|
||||
}
|
||||
|
||||
settings.ServerSelection.Cities, err = r.env.CSVInside("CITY", constants.ExpressvpnCityChoices(servers))
|
||||
if err != nil {
|
||||
return fmt.Errorf("environment variable CITY: %w", err)
|
||||
}
|
||||
|
||||
settings.ServerSelection.OpenVPN.TCP, err = readOpenVPNProtocol(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -26,5 +26,5 @@ func (settings *Provider) readFastestvpn(r reader) (err error) {
|
||||
return fmt.Errorf("environment variable COUNTRY: %w", err)
|
||||
}
|
||||
|
||||
return settings.ServerSelection.OpenVPN.readProtocolOnly(r.env)
|
||||
return settings.ServerSelection.OpenVPN.readProtocolOnly(r)
|
||||
}
|
||||
|
||||
@@ -5,14 +5,14 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/qdm12/golibs/logging"
|
||||
"github.com/qdm12/golibs/params"
|
||||
)
|
||||
|
||||
// Health contains settings for the healthcheck and health server.
|
||||
type Health struct {
|
||||
ServerAddress string
|
||||
OpenVPN HealthyWait
|
||||
AddressToPing string
|
||||
VPN HealthyWait
|
||||
}
|
||||
|
||||
func (settings *Health) String() string {
|
||||
@@ -24,8 +24,10 @@ func (settings *Health) lines() (lines []string) {
|
||||
|
||||
lines = append(lines, indent+lastIndent+"Server address: "+settings.ServerAddress)
|
||||
|
||||
lines = append(lines, indent+lastIndent+"OpenVPN:")
|
||||
for _, line := range settings.OpenVPN.lines() {
|
||||
lines = append(lines, indent+lastIndent+"Address to ping: "+settings.AddressToPing)
|
||||
|
||||
lines = append(lines, indent+lastIndent+"VPN:")
|
||||
for _, line := range settings.VPN.lines() {
|
||||
lines = append(lines, indent+indent+line)
|
||||
}
|
||||
|
||||
@@ -33,8 +35,8 @@ func (settings *Health) lines() (lines []string) {
|
||||
}
|
||||
|
||||
// Read is to be used for the healthcheck query mode.
|
||||
func (settings *Health) Read(env params.Interface, logger logging.Logger) (err error) {
|
||||
reader := newReader(env, models.AllServers{}, logger) // note: no need for servers data
|
||||
func (settings *Health) Read(env params.Interface, warner Warner) (err error) {
|
||||
reader := newReader(env, models.AllServers{}, warner) // note: no need for servers data
|
||||
return settings.read(reader)
|
||||
}
|
||||
|
||||
@@ -43,20 +45,27 @@ func (settings *Health) read(r reader) (err error) {
|
||||
settings.ServerAddress, warning, err = r.env.ListeningAddress(
|
||||
"HEALTH_SERVER_ADDRESS", params.Default("127.0.0.1:9999"))
|
||||
if warning != "" {
|
||||
r.logger.Warn("environment variable HEALTH_SERVER_ADDRESS: " + warning)
|
||||
r.warner.Warn("environment variable HEALTH_SERVER_ADDRESS: " + warning)
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("environment variable HEALTH_SERVER_ADDRESS: %w", err)
|
||||
}
|
||||
|
||||
settings.OpenVPN.Initial, err = r.env.Duration("HEALTH_OPENVPN_DURATION_INITIAL", params.Default("6s"))
|
||||
settings.AddressToPing, err = r.env.Get("HEALTH_ADDRESS_TO_PING", params.Default("github.com"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("environment variable HEALTH_OPENVPN_DURATION_INITIAL: %w", err)
|
||||
return fmt.Errorf("environment variable HEALTH_ADDRESS_TO_PING: %w", err)
|
||||
}
|
||||
|
||||
settings.OpenVPN.Addition, err = r.env.Duration("HEALTH_OPENVPN_DURATION_ADDITION", params.Default("5s"))
|
||||
retroKeyOption := params.RetroKeys([]string{"HEALTH_OPENVPN_DURATION_INITIAL"}, r.onRetroActive)
|
||||
settings.VPN.Initial, err = r.env.Duration("HEALTH_VPN_DURATION_INITIAL", params.Default("6s"), retroKeyOption)
|
||||
if err != nil {
|
||||
return fmt.Errorf("environment variable HEALTH_OPENVPN_DURATION_ADDITION: %w", err)
|
||||
return fmt.Errorf("environment variable HEALTH_VPN_DURATION_INITIAL: %w", err)
|
||||
}
|
||||
|
||||
retroKeyOption = params.RetroKeys([]string{"HEALTH_OPENVPN_DURATION_ADDITION"}, r.onRetroActive)
|
||||
settings.VPN.Addition, err = r.env.Duration("HEALTH_VPN_DURATION_ADDITION", params.Default("5s"), retroKeyOption)
|
||||
if err != nil {
|
||||
return fmt.Errorf("environment variable HEALTH_VPN_DURATION_ADDITION: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/qdm12/golibs/logging/mock_logging"
|
||||
"github.com/qdm12/golibs/params/mock_params"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -15,8 +14,15 @@ import (
|
||||
func Test_Health_String(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var health Health
|
||||
const expected = "|--Health:\n |--Server address: \n |--OpenVPN:\n |--Initial duration: 0s"
|
||||
health := Health{
|
||||
ServerAddress: "a",
|
||||
AddressToPing: "b",
|
||||
}
|
||||
const expected = `|--Health:
|
||||
|--Server address: a
|
||||
|--Address to ping: b
|
||||
|--VPN:
|
||||
|--Initial duration: 0s`
|
||||
|
||||
s := health.String()
|
||||
|
||||
@@ -34,14 +40,16 @@ func Test_Health_lines(t *testing.T) {
|
||||
lines: []string{
|
||||
"|--Health:",
|
||||
" |--Server address: ",
|
||||
" |--OpenVPN:",
|
||||
" |--Address to ping: ",
|
||||
" |--VPN:",
|
||||
" |--Initial duration: 0s",
|
||||
},
|
||||
},
|
||||
"filled settings": {
|
||||
settings: Health{
|
||||
ServerAddress: "address:9999",
|
||||
OpenVPN: HealthyWait{
|
||||
AddressToPing: "github.com",
|
||||
VPN: HealthyWait{
|
||||
Initial: time.Second,
|
||||
Addition: time.Minute,
|
||||
},
|
||||
@@ -49,7 +57,8 @@ func Test_Health_lines(t *testing.T) {
|
||||
lines: []string{
|
||||
"|--Health:",
|
||||
" |--Server address: address:9999",
|
||||
" |--OpenVPN:",
|
||||
" |--Address to ping: github.com",
|
||||
" |--VPN:",
|
||||
" |--Initial duration: 1s",
|
||||
" |--Addition duration: 1m0s",
|
||||
},
|
||||
@@ -73,62 +82,127 @@ func Test_Health_read(t *testing.T) {
|
||||
|
||||
errDummy := errors.New("dummy")
|
||||
|
||||
type stringCall struct {
|
||||
call bool
|
||||
s string
|
||||
err error
|
||||
}
|
||||
|
||||
type stringCallWithWarning struct {
|
||||
call bool
|
||||
s string
|
||||
warning string
|
||||
err error
|
||||
}
|
||||
|
||||
type durationCall struct {
|
||||
call bool
|
||||
duration time.Duration
|
||||
err error
|
||||
}
|
||||
|
||||
testCases := map[string]struct {
|
||||
openvpnInitialDuration time.Duration
|
||||
openvpnInitialErr error
|
||||
openvpnAdditionDuration time.Duration
|
||||
openvpnAdditionErr error
|
||||
serverAddress string
|
||||
serverAddressWarning string
|
||||
serverAddressErr error
|
||||
expected Health
|
||||
err error
|
||||
serverAddress stringCallWithWarning
|
||||
addressToPing stringCall
|
||||
vpnInitial durationCall
|
||||
vpnAddition durationCall
|
||||
expected Health
|
||||
err error
|
||||
}{
|
||||
"success": {
|
||||
openvpnInitialDuration: time.Second,
|
||||
openvpnAdditionDuration: time.Minute,
|
||||
serverAddress: "127.0.0.1:9999",
|
||||
serverAddress: stringCallWithWarning{
|
||||
call: true,
|
||||
s: "127.0.0.1:9999",
|
||||
},
|
||||
addressToPing: stringCall{
|
||||
call: true,
|
||||
s: "1.2.3.4",
|
||||
},
|
||||
vpnInitial: durationCall{
|
||||
call: true,
|
||||
duration: time.Second,
|
||||
},
|
||||
vpnAddition: durationCall{
|
||||
call: true,
|
||||
duration: time.Minute,
|
||||
},
|
||||
expected: Health{
|
||||
ServerAddress: "127.0.0.1:9999",
|
||||
OpenVPN: HealthyWait{
|
||||
AddressToPing: "1.2.3.4",
|
||||
VPN: HealthyWait{
|
||||
Initial: time.Second,
|
||||
Addition: time.Minute,
|
||||
},
|
||||
},
|
||||
},
|
||||
"listening address error": {
|
||||
openvpnInitialDuration: time.Second,
|
||||
openvpnAdditionDuration: time.Minute,
|
||||
serverAddress: "127.0.0.1:9999",
|
||||
serverAddressWarning: "warning",
|
||||
serverAddressErr: errDummy,
|
||||
serverAddress: stringCallWithWarning{
|
||||
call: true,
|
||||
s: "127.0.0.1:9999",
|
||||
warning: "warning",
|
||||
err: errDummy,
|
||||
},
|
||||
expected: Health{
|
||||
ServerAddress: "127.0.0.1:9999",
|
||||
},
|
||||
err: errors.New("environment variable HEALTH_SERVER_ADDRESS: dummy"),
|
||||
},
|
||||
"initial error": {
|
||||
openvpnInitialDuration: time.Second,
|
||||
openvpnInitialErr: errDummy,
|
||||
openvpnAdditionDuration: time.Minute,
|
||||
"address to ping error": {
|
||||
serverAddress: stringCallWithWarning{
|
||||
call: true,
|
||||
},
|
||||
addressToPing: stringCall{
|
||||
call: true,
|
||||
s: "address",
|
||||
err: errDummy,
|
||||
},
|
||||
expected: Health{
|
||||
OpenVPN: HealthyWait{
|
||||
AddressToPing: "address",
|
||||
},
|
||||
err: errors.New("environment variable HEALTH_ADDRESS_TO_PING: dummy"),
|
||||
},
|
||||
"initial error": {
|
||||
serverAddress: stringCallWithWarning{
|
||||
call: true,
|
||||
},
|
||||
addressToPing: stringCall{
|
||||
call: true,
|
||||
},
|
||||
vpnInitial: durationCall{
|
||||
call: true,
|
||||
duration: time.Second,
|
||||
err: errDummy,
|
||||
},
|
||||
expected: Health{
|
||||
VPN: HealthyWait{
|
||||
Initial: time.Second,
|
||||
},
|
||||
},
|
||||
err: errors.New("environment variable HEALTH_OPENVPN_DURATION_INITIAL: dummy"),
|
||||
err: errors.New("environment variable HEALTH_VPN_DURATION_INITIAL: dummy"),
|
||||
},
|
||||
"addition error": {
|
||||
openvpnInitialDuration: time.Second,
|
||||
openvpnAdditionDuration: time.Minute,
|
||||
openvpnAdditionErr: errDummy,
|
||||
serverAddress: stringCallWithWarning{
|
||||
call: true,
|
||||
},
|
||||
addressToPing: stringCall{
|
||||
call: true,
|
||||
},
|
||||
vpnInitial: durationCall{
|
||||
call: true,
|
||||
duration: time.Second,
|
||||
},
|
||||
vpnAddition: durationCall{
|
||||
call: true,
|
||||
duration: time.Minute,
|
||||
err: errDummy,
|
||||
},
|
||||
expected: Health{
|
||||
OpenVPN: HealthyWait{
|
||||
VPN: HealthyWait{
|
||||
Initial: time.Second,
|
||||
Addition: time.Minute,
|
||||
},
|
||||
},
|
||||
err: errors.New("environment variable HEALTH_OPENVPN_DURATION_ADDITION: dummy"),
|
||||
err: errors.New("environment variable HEALTH_VPN_DURATION_ADDITION: dummy"),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -140,29 +214,45 @@ func Test_Health_read(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
|
||||
env := mock_params.NewMockInterface(ctrl)
|
||||
logger := mock_logging.NewMockLogger(ctrl)
|
||||
warner := NewMockWarner(ctrl)
|
||||
|
||||
env.EXPECT().ListeningAddress("HEALTH_SERVER_ADDRESS", gomock.Any()).
|
||||
Return(testCase.serverAddress, testCase.serverAddressWarning,
|
||||
testCase.serverAddressErr)
|
||||
if testCase.serverAddressWarning != "" {
|
||||
logger.EXPECT().Warn("environment variable HEALTH_SERVER_ADDRESS: " + testCase.serverAddressWarning)
|
||||
if testCase.serverAddress.call {
|
||||
value := testCase.serverAddress.s
|
||||
warning := testCase.serverAddress.warning
|
||||
err := testCase.serverAddress.err
|
||||
env.EXPECT().ListeningAddress("HEALTH_SERVER_ADDRESS", gomock.Any()).
|
||||
Return(value, warning, err)
|
||||
if warning != "" {
|
||||
warner.EXPECT().Warn("environment variable HEALTH_SERVER_ADDRESS: " + warning)
|
||||
}
|
||||
}
|
||||
|
||||
if testCase.serverAddressErr == nil {
|
||||
if testCase.addressToPing.call {
|
||||
value := testCase.addressToPing.s
|
||||
err := testCase.addressToPing.err
|
||||
env.EXPECT().Get("HEALTH_ADDRESS_TO_PING", gomock.Any()).
|
||||
Return(value, err)
|
||||
}
|
||||
|
||||
if testCase.vpnInitial.call {
|
||||
value := testCase.vpnInitial.duration
|
||||
err := testCase.vpnInitial.err
|
||||
env.EXPECT().
|
||||
Duration("HEALTH_OPENVPN_DURATION_INITIAL", gomock.Any()).
|
||||
Return(testCase.openvpnInitialDuration, testCase.openvpnInitialErr)
|
||||
if testCase.openvpnInitialErr == nil {
|
||||
env.EXPECT().
|
||||
Duration("HEALTH_OPENVPN_DURATION_ADDITION", gomock.Any()).
|
||||
Return(testCase.openvpnAdditionDuration, testCase.openvpnAdditionErr)
|
||||
}
|
||||
Duration("HEALTH_VPN_DURATION_INITIAL", gomock.Any()).
|
||||
Return(value, err)
|
||||
}
|
||||
|
||||
if testCase.vpnAddition.call {
|
||||
value := testCase.vpnAddition.duration
|
||||
err := testCase.vpnAddition.err
|
||||
env.EXPECT().
|
||||
Duration("HEALTH_VPN_DURATION_ADDITION", gomock.Any()).
|
||||
Return(value, err)
|
||||
}
|
||||
|
||||
r := reader{
|
||||
env: env,
|
||||
logger: logger,
|
||||
warner: warner,
|
||||
}
|
||||
|
||||
var health Health
|
||||
|
||||
@@ -36,5 +36,5 @@ func (settings *Provider) readHideMyAss(r reader) (err error) {
|
||||
return fmt.Errorf("environment variable SERVER_HOSTNAME: %w", err)
|
||||
}
|
||||
|
||||
return settings.ServerSelection.OpenVPN.readProtocolAndPort(r.env)
|
||||
return settings.ServerSelection.OpenVPN.readProtocolAndPort(r)
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ func (settings *HTTPProxy) read(r reader) (err error) {
|
||||
settings.Port, warning, err = r.env.ListeningPort("HTTPPROXY_PORT", params.Default("8888"),
|
||||
params.RetroKeys([]string{"TINYPROXY_PORT", "PROXY_PORT"}, r.onRetroActive))
|
||||
if len(warning) > 0 {
|
||||
r.logger.Warn(warning)
|
||||
r.warner.Warn(warning)
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("environment variable HTTPPROXY_PORT (or TINYPROXY_PORT, PROXY_PORT): %w", err)
|
||||
|
||||
@@ -31,5 +31,5 @@ func (settings *Provider) readIpvanish(r reader) (err error) {
|
||||
return fmt.Errorf("environment variable SERVER_HOSTNAME: %w", err)
|
||||
}
|
||||
|
||||
return settings.ServerSelection.OpenVPN.readProtocolOnly(r.env)
|
||||
return settings.ServerSelection.OpenVPN.readProtocolOnly(r)
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ func Test_Provider_readIpvanish(t *testing.T) {
|
||||
settings: Provider{
|
||||
Name: constants.Ipvanish,
|
||||
},
|
||||
err: errors.New("environment variable PROTOCOL: dummy test error"),
|
||||
err: errors.New("environment variable OPENVPN_PROTOCOL: dummy test error"),
|
||||
},
|
||||
"default settings": {
|
||||
targetIP: singleStringCall{call: true},
|
||||
@@ -145,7 +145,7 @@ func Test_Provider_readIpvanish(t *testing.T) {
|
||||
Return(testCase.hostnames.values, testCase.hostnames.err)
|
||||
}
|
||||
if testCase.protocol.call {
|
||||
env.EXPECT().Inside("PROTOCOL", []string{constants.TCP, constants.UDP}, gomock.Any()).
|
||||
env.EXPECT().Inside("OPENVPN_PROTOCOL", []string{constants.TCP, constants.UDP}, gomock.Any()).
|
||||
Return(testCase.protocol.value, testCase.protocol.err)
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ func (settings *Provider) readIvpn(r reader) (err error) {
|
||||
return fmt.Errorf("environment variable SERVER_HOSTNAME: %w", err)
|
||||
}
|
||||
|
||||
err = settings.ServerSelection.OpenVPN.readIVPN(r.env)
|
||||
err = settings.ServerSelection.OpenVPN.readIVPN(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -44,14 +44,17 @@ func (settings *Provider) readIvpn(r reader) (err error) {
|
||||
return settings.ServerSelection.Wireguard.readIVPN(r.env)
|
||||
}
|
||||
|
||||
func (settings *OpenVPNSelection) readIVPN(env params.Interface) (err error) {
|
||||
settings.TCP, err = readProtocol(env)
|
||||
func (settings *OpenVPNSelection) readIVPN(r reader) (err error) {
|
||||
settings.TCP, err = readOpenVPNProtocol(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.CustomPort, err = readOpenVPNCustomPort(env, settings.TCP,
|
||||
[]uint16{80, 443, 1443}, []uint16{53, 1194, 2049, 2050})
|
||||
settings.CustomPort, err = readOpenVPNCustomPort(r, openvpnPortValidation{
|
||||
tcp: settings.TCP,
|
||||
allowedTCP: []uint16{80, 443, 1443},
|
||||
allowedUDP: []uint16{53, 1194, 2049, 2050},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -60,7 +63,7 @@ func (settings *OpenVPNSelection) readIVPN(env params.Interface) (err error) {
|
||||
}
|
||||
|
||||
func (settings *WireguardSelection) readIVPN(env params.Interface) (err error) {
|
||||
settings.CustomPort, err = readWireguardCustomPort(env,
|
||||
settings.EndpointPort, err = readWireguardCustomPort(env,
|
||||
[]uint16{2049, 2050, 53, 30587, 41893, 48574, 58237})
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -40,16 +40,18 @@ func Test_Provider_readIvpn(t *testing.T) { //nolint:gocognit
|
||||
}
|
||||
|
||||
testCases := map[string]struct {
|
||||
targetIP singleStringCall
|
||||
countries sliceStringCall
|
||||
cities sliceStringCall
|
||||
isps sliceStringCall
|
||||
hostnames sliceStringCall
|
||||
protocol singleStringCall
|
||||
ovpnPort portCall
|
||||
wgPort portCall
|
||||
settings Provider
|
||||
err error
|
||||
targetIP singleStringCall
|
||||
countries sliceStringCall
|
||||
cities sliceStringCall
|
||||
isps sliceStringCall
|
||||
hostnames sliceStringCall
|
||||
protocol singleStringCall
|
||||
ovpnPort portCall
|
||||
ovpnOldPort portCall
|
||||
wgPort portCall
|
||||
wgOldPort portCall
|
||||
settings Provider
|
||||
err error
|
||||
}{
|
||||
"target IP error": {
|
||||
targetIP: singleStringCall{call: true, value: "something", err: errDummy},
|
||||
@@ -106,7 +108,7 @@ func Test_Provider_readIvpn(t *testing.T) { //nolint:gocognit
|
||||
settings: Provider{
|
||||
Name: constants.Ivpn,
|
||||
},
|
||||
err: errors.New("environment variable PROTOCOL: dummy test error"),
|
||||
err: errors.New("environment variable OPENVPN_PROTOCOL: dummy test error"),
|
||||
},
|
||||
"openvpn custom port error": {
|
||||
targetIP: singleStringCall{call: true},
|
||||
@@ -119,31 +121,34 @@ func Test_Provider_readIvpn(t *testing.T) { //nolint:gocognit
|
||||
settings: Provider{
|
||||
Name: constants.Ivpn,
|
||||
},
|
||||
err: errors.New("environment variable PORT: dummy test error"),
|
||||
err: errors.New("environment variable OPENVPN_PORT: dummy test error"),
|
||||
},
|
||||
"wireguard custom port error": {
|
||||
targetIP: singleStringCall{call: true},
|
||||
countries: sliceStringCall{call: true},
|
||||
cities: sliceStringCall{call: true},
|
||||
isps: sliceStringCall{call: true},
|
||||
hostnames: sliceStringCall{call: true},
|
||||
protocol: singleStringCall{call: true},
|
||||
ovpnPort: portCall{getCall: true, getValue: "0"},
|
||||
wgPort: portCall{getCall: true, getErr: errDummy},
|
||||
targetIP: singleStringCall{call: true},
|
||||
countries: sliceStringCall{call: true},
|
||||
cities: sliceStringCall{call: true},
|
||||
isps: sliceStringCall{call: true},
|
||||
hostnames: sliceStringCall{call: true},
|
||||
protocol: singleStringCall{call: true},
|
||||
ovpnPort: portCall{getCall: true, getValue: "0"},
|
||||
ovpnOldPort: portCall{getCall: true, getValue: "0"},
|
||||
wgPort: portCall{getCall: true, getErr: errDummy},
|
||||
settings: Provider{
|
||||
Name: constants.Ivpn,
|
||||
},
|
||||
err: errors.New("environment variable WIREGUARD_PORT: dummy test error"),
|
||||
err: errors.New("environment variable WIREGUARD_ENDPOINT_PORT: dummy test error"),
|
||||
},
|
||||
"default settings": {
|
||||
targetIP: singleStringCall{call: true},
|
||||
countries: sliceStringCall{call: true},
|
||||
cities: sliceStringCall{call: true},
|
||||
isps: sliceStringCall{call: true},
|
||||
hostnames: sliceStringCall{call: true},
|
||||
protocol: singleStringCall{call: true},
|
||||
ovpnPort: portCall{getCall: true, getValue: "0"},
|
||||
wgPort: portCall{getCall: true, getValue: "0"},
|
||||
targetIP: singleStringCall{call: true},
|
||||
countries: sliceStringCall{call: true},
|
||||
cities: sliceStringCall{call: true},
|
||||
isps: sliceStringCall{call: true},
|
||||
hostnames: sliceStringCall{call: true},
|
||||
protocol: singleStringCall{call: true},
|
||||
ovpnPort: portCall{getCall: true, getValue: "0"},
|
||||
ovpnOldPort: portCall{getCall: true, getValue: "0"},
|
||||
wgPort: portCall{getCall: true, getValue: "0"},
|
||||
wgOldPort: portCall{getCall: true, getValue: "0"},
|
||||
settings: Provider{
|
||||
Name: constants.Ivpn,
|
||||
},
|
||||
@@ -165,7 +170,7 @@ func Test_Provider_readIvpn(t *testing.T) { //nolint:gocognit
|
||||
CustomPort: 443,
|
||||
},
|
||||
Wireguard: WireguardSelection{
|
||||
CustomPort: 2049,
|
||||
EndpointPort: 2049,
|
||||
},
|
||||
TargetIP: net.IPv4(1, 2, 3, 4),
|
||||
Countries: []string{"A", "B"},
|
||||
@@ -212,25 +217,41 @@ func Test_Provider_readIvpn(t *testing.T) { //nolint:gocognit
|
||||
Return(testCase.hostnames.values, testCase.hostnames.err)
|
||||
}
|
||||
if testCase.protocol.call {
|
||||
env.EXPECT().Inside("PROTOCOL", []string{constants.TCP, constants.UDP}, gomock.Any()).
|
||||
env.EXPECT().Inside("OPENVPN_PROTOCOL", []string{constants.TCP, constants.UDP}, gomock.Any()).
|
||||
Return(testCase.protocol.value, testCase.protocol.err)
|
||||
}
|
||||
if testCase.ovpnPort.getCall {
|
||||
env.EXPECT().Get("PORT", gomock.Any()).
|
||||
env.EXPECT().Get("OPENVPN_PORT", gomock.Any()).
|
||||
Return(testCase.ovpnPort.getValue, testCase.ovpnPort.getErr)
|
||||
}
|
||||
if testCase.ovpnPort.portCall {
|
||||
env.EXPECT().Port("PORT").
|
||||
env.EXPECT().Port("OPENVPN_PORT").
|
||||
Return(testCase.ovpnPort.portValue, testCase.ovpnPort.portErr)
|
||||
}
|
||||
if testCase.ovpnOldPort.getCall {
|
||||
env.EXPECT().Get("PORT", gomock.Any()).
|
||||
Return(testCase.ovpnOldPort.getValue, testCase.ovpnOldPort.getErr)
|
||||
}
|
||||
if testCase.ovpnOldPort.portCall {
|
||||
env.EXPECT().Port("PORT").
|
||||
Return(testCase.ovpnOldPort.portValue, testCase.ovpnOldPort.portErr)
|
||||
}
|
||||
if testCase.wgPort.getCall {
|
||||
env.EXPECT().Get("WIREGUARD_PORT", gomock.Any()).
|
||||
env.EXPECT().Get("WIREGUARD_ENDPOINT_PORT", gomock.Any()).
|
||||
Return(testCase.wgPort.getValue, testCase.wgPort.getErr)
|
||||
}
|
||||
if testCase.wgPort.portCall {
|
||||
env.EXPECT().Port("WIREGUARD_PORT").
|
||||
env.EXPECT().Port("WIREGUARD_ENDPOINT_PORT").
|
||||
Return(testCase.wgPort.portValue, testCase.wgPort.portErr)
|
||||
}
|
||||
if testCase.wgOldPort.getCall {
|
||||
env.EXPECT().Get("WIREGUARD_PORT", gomock.Any()).
|
||||
Return(testCase.wgOldPort.getValue, testCase.wgOldPort.getErr)
|
||||
}
|
||||
if testCase.wgOldPort.portCall {
|
||||
env.EXPECT().Port("WIREGUARD_PORT").
|
||||
Return(testCase.wgOldPort.portValue, testCase.wgOldPort.portErr)
|
||||
}
|
||||
|
||||
r := reader{
|
||||
servers: allServers,
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
package configuration
|
||||
|
||||
import (
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/openvpn/parse"
|
||||
)
|
||||
|
||||
var (
|
||||
errClientCert = errors.New("cannot read client certificate")
|
||||
errClientKey = errors.New("cannot read client key")
|
||||
)
|
||||
|
||||
func readClientKey(r reader) (clientKey string, err error) {
|
||||
@@ -13,22 +17,7 @@ func readClientKey(r reader) (clientKey string, err error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return extractClientKey(b)
|
||||
}
|
||||
|
||||
var errDecodePEMBlockClientKey = errors.New("cannot decode PEM block from client key")
|
||||
|
||||
func extractClientKey(b []byte) (key string, err error) {
|
||||
pemBlock, _ := pem.Decode(b)
|
||||
if pemBlock == nil {
|
||||
return "", errDecodePEMBlockClientKey
|
||||
}
|
||||
parsedBytes := pem.EncodeToMemory(pemBlock)
|
||||
s := string(parsedBytes)
|
||||
s = strings.ReplaceAll(s, "\n", "")
|
||||
s = strings.TrimPrefix(s, "-----BEGIN PRIVATE KEY-----")
|
||||
s = strings.TrimSuffix(s, "-----END PRIVATE KEY-----")
|
||||
return s, nil
|
||||
return parse.ExtractPrivateKey(b)
|
||||
}
|
||||
|
||||
func readClientCertificate(r reader) (clientCertificate string, err error) {
|
||||
@@ -36,20 +25,5 @@ func readClientCertificate(r reader) (clientCertificate string, err error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return extractClientCertificate(b)
|
||||
}
|
||||
|
||||
var errDecodePEMBlockClientCert = errors.New("cannot decode PEM block from client certificate")
|
||||
|
||||
func extractClientCertificate(b []byte) (certificate string, err error) {
|
||||
pemBlock, _ := pem.Decode(b)
|
||||
if pemBlock == nil {
|
||||
return "", errDecodePEMBlockClientCert
|
||||
}
|
||||
parsedBytes := pem.EncodeToMemory(pemBlock)
|
||||
s := string(parsedBytes)
|
||||
s = strings.ReplaceAll(s, "\n", "")
|
||||
s = strings.TrimPrefix(s, "-----BEGIN CERTIFICATE-----")
|
||||
s = strings.TrimSuffix(s, "-----END CERTIFICATE-----")
|
||||
return s, nil
|
||||
return parse.ExtractCert(b)
|
||||
}
|
||||
|
||||
@@ -1,174 +0,0 @@
|
||||
package configuration
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_extractClientKey(t *testing.T) {
|
||||
t.Parallel()
|
||||
const validPEM = `
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
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
|
||||
zZjrL52saevO25cigVl+hxcnY8DTpbk=
|
||||
-----END PRIVATE KEY-----
|
||||
`
|
||||
const validKeyString = "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" //nolint:lll
|
||||
testCases := map[string]struct {
|
||||
b []byte
|
||||
key string
|
||||
err error
|
||||
}{
|
||||
"no input": {
|
||||
err: errDecodePEMBlockClientKey,
|
||||
},
|
||||
"bad input": {
|
||||
b: []byte{1, 2, 3},
|
||||
err: errDecodePEMBlockClientKey,
|
||||
},
|
||||
"valid key": {
|
||||
b: []byte(validPEM),
|
||||
key: validKeyString,
|
||||
},
|
||||
}
|
||||
for name, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
key, err := extractClientKey(testCase.b)
|
||||
if testCase.err != nil {
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, testCase.err.Error(), err.Error())
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
assert.Equal(t, testCase.key, key)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_extractClientCertificate(t *testing.T) {
|
||||
t.Parallel()
|
||||
const validPEM = `
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIGrDCCBJSgAwIBAgIEAdTnfTANBgkqhkiG9w0BAQsFADB7MQswCQYDVQQGEwJS
|
||||
TzESMBAGA1UEBxMJQnVjaGFyZXN0MRgwFgYDVQQKEw9DeWJlckdob3N0IFMuQS4x
|
||||
GzAZBgNVBAMTEkN5YmVyR2hvc3QgUm9vdCBDQTEhMB8GCSqGSIb3DQEJARYSaW5m
|
||||
b0BjeWJlcmdob3N0LnJvMB4XDTIwMDcwNDE1MjkzNloXDTMwMDcwMjE1MjkzNlow
|
||||
fTELMAkGA1UEBhMCUk8xEjAQBgNVBAcMCUJ1Y2hhcmVzdDEYMBYGA1UECgwPQ3li
|
||||
ZXJHaG9zdCBTLkEuMR0wGwYDVQQDDBRjLmoua2xhdmVyQGdtYWlsLmNvbTEhMB8G
|
||||
CSqGSIb3DQEJARYSaW5mb0BjeWJlcmdob3N0LnJvMIICIjANBgkqhkiG9w0BAQEF
|
||||
AAOCAg8AMIICCgKCAgEAobp2NlGUHMNBe08YEOnVG3QJjF3ZaXbRhE/II9rmtgJT
|
||||
NZtDohGChvFlNRsExKzVrKxHCeuJkVffwzQ6fYk4/M1RdYLJUh0UVw3e4WdApw8E
|
||||
7TJZxDYm4SHQNXUvt1Rt5TjslcXxIpDZgrMSc/kHROYEL9tdgdzPZErUJehXyJPh
|
||||
EzIrzmAJh501x7WwKPz9ctSVlItyavqEWFF2vyUa6X9DYmD9mQTz5c+VXNO5DkXm
|
||||
PFBIaEVDnvFtcjGJ56yEvFnWVukL+OUX7ezowrIOFOcp9udjgpeiHq+XvsQ6ER0D
|
||||
Jt25MiEId3NjkxtZ8BitDftTcLN/kt81hWKT7adMVc3kpIZ80cxrwRCttMd7sHAz
|
||||
KI9u7pMxv10eUOsIEY87ewBe3l6KvEnjA+9uIjim6gLLebDIaEH50Ee9PzNJ8fqQ
|
||||
2u54Ab4bt00/H1sUnJ6Ss/+WsQDOK1BsPRKKcnHZntOlHrs2Tu5+txKNU2cOapI8
|
||||
SjVULUNKrRXASbpfWnLUfri/HO742bJb/TjkOJcOxta3hTPFAhaRWBusVlB41XVH
|
||||
euH5DAhugYXeSNK6/6Ul8YvKUNH/7QbxuGIGXfth19Xl4QLI1umyEjZopSlt3tOi
|
||||
O2V1soVNSQCCfxXVoCTMESMLjhkjWdmBDhdy2GTW7S4YoJfqVKiS18rYkN7I4ZMC
|
||||
AwEAAaOCATQwggEwMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgWgMDQGCWCGSAGG+EIB
|
||||
DQQnFiVDeWJlckdob3N0IEdlbmVyYXRlZCBVc2VyIENlcnRpZmljYXRlMBEGCWCG
|
||||
SAGG+EIBAQQEAwIHgDAdBgNVHQ4EFgQULwUtU5s6pL2NN9gPeEnKX0dhwiswga0G
|
||||
A1UdIwSBpTCBooAU6tdK1g/He5qzjeAoM5eHt4in9iWhf6R9MHsxCzAJBgNVBAYT
|
||||
AlJPMRIwEAYDVQQHEwlCdWNoYXJlc3QxGDAWBgNVBAoTD0N5YmVyR2hvc3QgUy5B
|
||||
LjEbMBkGA1UEAxMSQ3liZXJHaG9zdCBSb290IENBMSEwHwYJKoZIhvcNAQkBFhJp
|
||||
bmZvQGN5YmVyZ2hvc3Qucm+CCQCcVButZsQ0uzANBgkqhkiG9w0BAQsFAAOCAgEA
|
||||
ystGIMYhQWaEdTqlnLCytrr8657t+PuidZMNNIaPB3wN2Fi2xKf14DTg03mqxjmP
|
||||
Pb+f+PVNIOV5PdWD4jcQwOP1GEboGV0DFzlRGeAtDcvKwdee4oASJbZq1CETqDao
|
||||
hQTxKEWC+UBk2F36nOaEI6Sab+Mb4cR9//PAwvzOqrXuGF5NuIOX7eFtCMQSgQq6
|
||||
lRRqTQjekm0Dxigx4JA92Jo2qZRwCJ0T3IXBJGL831HCFJbDWv8PV3lsfFb/i2+v
|
||||
r54uywFQVWWp18dYi97gipfuQ4zRg2Ldx5aXSmnhhKpg5ioZvtk043QofF12YORh
|
||||
obElqavRbvvhZvlCouvcuoq9QKi7IPe5SJZkZ1X7ezMesCwBzwFpt6vRUAcslsNF
|
||||
bcYS1iSENlY/PTcDqBhbKuc9yAhq+/aUgaY/8VF5RWVzSRZufbf3BPwOkE4K0Uyb
|
||||
aobO/YX0JOkCacAD+4tdR6YSXNIMMRAOCBQvxbxFXaHzhwhzBAjdsC56FrJKwXvQ
|
||||
rRLU3tF4P0zFMeNTay8uTtUXugDK7EnklLESuYdpUJ8bUMlAUhJBi6UFI9/icMud
|
||||
xXvLRvhnBW9EtKib5JnVFUovcEUt+3EJbyst05nkL4YPjQS4TC9DHdo5SyRAy1Tp
|
||||
iOCYTbretAFZRhh6ycUN5hBeN8GMQxiMreMtDV4PEIQ=
|
||||
-----END CERTIFICATE-----
|
||||
`
|
||||
const validCertificateString = "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" //nolint:lll
|
||||
testCases := map[string]struct {
|
||||
b []byte
|
||||
certificate string
|
||||
err error
|
||||
}{
|
||||
"no input": {
|
||||
err: errDecodePEMBlockClientCert,
|
||||
},
|
||||
"bad input": {
|
||||
b: []byte{1, 2, 3},
|
||||
err: errDecodePEMBlockClientCert,
|
||||
},
|
||||
"valid key": {
|
||||
b: []byte(validPEM),
|
||||
certificate: validCertificateString,
|
||||
},
|
||||
}
|
||||
for name, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
certificate, err := extractClientCertificate(testCase.b)
|
||||
if testCase.err != nil {
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, testCase.err.Error(), err.Error())
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
assert.Equal(t, testCase.certificate, certificate)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -41,7 +41,7 @@ func (settings *Provider) readMullvad(r reader) (err error) {
|
||||
return fmt.Errorf("environment variable OWNED: %w", err)
|
||||
}
|
||||
|
||||
err = settings.ServerSelection.OpenVPN.readMullvad(r.env)
|
||||
err = settings.ServerSelection.OpenVPN.readMullvad(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -49,14 +49,17 @@ func (settings *Provider) readMullvad(r reader) (err error) {
|
||||
return settings.ServerSelection.Wireguard.readMullvad(r.env)
|
||||
}
|
||||
|
||||
func (settings *OpenVPNSelection) readMullvad(env params.Interface) (err error) {
|
||||
settings.TCP, err = readProtocol(env)
|
||||
func (settings *OpenVPNSelection) readMullvad(r reader) (err error) {
|
||||
settings.TCP, err = readOpenVPNProtocol(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.CustomPort, err = readOpenVPNCustomPort(env, settings.TCP,
|
||||
[]uint16{80, 443, 1401}, []uint16{53, 1194, 1195, 1196, 1197, 1300, 1301, 1302, 1303, 1400})
|
||||
settings.CustomPort, err = readOpenVPNCustomPort(r, openvpnPortValidation{
|
||||
tcp: settings.TCP,
|
||||
allowedTCP: []uint16{80, 443, 1401},
|
||||
allowedUDP: []uint16{53, 1194, 1195, 1196, 1197, 1300, 1301, 1302, 1303, 1400},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -65,7 +68,7 @@ func (settings *OpenVPNSelection) readMullvad(env params.Interface) (err error)
|
||||
}
|
||||
|
||||
func (settings *WireguardSelection) readMullvad(env params.Interface) (err error) {
|
||||
settings.CustomPort, err = readWireguardCustomPort(env, nil)
|
||||
settings.EndpointPort, err = readWireguardCustomPort(env, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -27,17 +27,12 @@ func (settings *Provider) readNordvpn(r reader) (err error) {
|
||||
return fmt.Errorf("environment variable SERVER_HOSTNAME: %w", err)
|
||||
}
|
||||
|
||||
settings.ServerSelection.Names, err = r.env.CSVInside("SERVER_NAME", constants.NordvpnHostnameChoices(servers))
|
||||
if err != nil {
|
||||
return fmt.Errorf("environment variable SERVER_NAME: %w", err)
|
||||
}
|
||||
|
||||
settings.ServerSelection.Numbers, err = readNordVPNServerNumbers(r.env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return settings.ServerSelection.OpenVPN.readProtocolOnly(r.env)
|
||||
return settings.ServerSelection.OpenVPN.readProtocolOnly(r)
|
||||
}
|
||||
|
||||
func readNordVPNServerNumbers(env params.Interface) (numbers []uint16, err error) {
|
||||
|
||||
@@ -19,9 +19,9 @@ type OpenVPN struct {
|
||||
Flags []string `json:"flags"`
|
||||
MSSFix uint16 `json:"mssfix"`
|
||||
Root bool `json:"run_as_root"`
|
||||
Cipher string `json:"cipher"`
|
||||
Ciphers []string `json:"ciphers"`
|
||||
Auth string `json:"auth"`
|
||||
Config string `json:"custom_config"`
|
||||
ConfFile string `json:"conf_file"`
|
||||
Version string `json:"version"`
|
||||
ClientCrt string `json:"-"` // Cyberghost
|
||||
ClientKey string `json:"-"` // Cyberghost, VPNUnlimited
|
||||
@@ -52,15 +52,15 @@ func (settings *OpenVPN) lines() (lines []string) {
|
||||
lines = append(lines, indent+lastIndent+"Run as root: enabled")
|
||||
}
|
||||
|
||||
if len(settings.Cipher) > 0 {
|
||||
lines = append(lines, indent+lastIndent+"Custom cipher: "+settings.Cipher)
|
||||
if len(settings.Ciphers) > 0 {
|
||||
lines = append(lines, indent+lastIndent+"Custom ciphers: "+commaJoin(settings.Ciphers))
|
||||
}
|
||||
if len(settings.Auth) > 0 {
|
||||
lines = append(lines, indent+lastIndent+"Custom auth algorithm: "+settings.Auth)
|
||||
}
|
||||
|
||||
if len(settings.Config) > 0 {
|
||||
lines = append(lines, indent+lastIndent+"Custom configuration: "+settings.Config)
|
||||
if settings.ConfFile != "" {
|
||||
lines = append(lines, indent+lastIndent+"Configuration file: "+settings.ConfFile)
|
||||
}
|
||||
|
||||
if settings.ClientKey != "" {
|
||||
@@ -83,13 +83,14 @@ func (settings *OpenVPN) lines() (lines []string) {
|
||||
}
|
||||
|
||||
func (settings *OpenVPN) read(r reader, serviceProvider string) (err error) {
|
||||
settings.Config, err = r.env.Get("OPENVPN_CUSTOM_CONFIG", params.CaseSensitiveValue())
|
||||
if err != nil {
|
||||
return fmt.Errorf("environment variable OPENVPN_CUSTOM_CONFIG: %w", err)
|
||||
credentialsRequired := false
|
||||
switch serviceProvider {
|
||||
case constants.Custom:
|
||||
case constants.VPNUnlimited:
|
||||
default:
|
||||
credentialsRequired = true
|
||||
}
|
||||
|
||||
credentialsRequired := settings.Config == "" && serviceProvider != constants.VPNUnlimited
|
||||
|
||||
settings.User, err = r.getFromEnvOrSecretFile("OPENVPN_USER", credentialsRequired, []string{"USER"})
|
||||
if err != nil {
|
||||
return fmt.Errorf("environment variable OPENVPN_USER: %w", err)
|
||||
@@ -126,12 +127,12 @@ func (settings *OpenVPN) read(r reader, serviceProvider string) (err error) {
|
||||
settings.Flags = strings.Fields(flagsStr)
|
||||
}
|
||||
|
||||
settings.Root, err = r.env.YesNo("OPENVPN_ROOT", params.Default("yes"))
|
||||
settings.Root, err = r.env.YesNo("OPENVPN_ROOT", params.Default("no"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("environment variable OPENVPN_ROOT: %w", err)
|
||||
}
|
||||
|
||||
settings.Cipher, err = r.env.Get("OPENVPN_CIPHER")
|
||||
settings.Ciphers, err = r.env.CSV("OPENVPN_CIPHER")
|
||||
if err != nil {
|
||||
return fmt.Errorf("environment variable OPENVPN_CIPHER: %w", err)
|
||||
}
|
||||
@@ -158,16 +159,17 @@ func (settings *OpenVPN) read(r reader, serviceProvider string) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.EncPreset, err = getPIAEncryptionPreset(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch serviceProvider {
|
||||
case constants.Custom:
|
||||
err = settings.readCustom(r) // read OPENVPN_CUSTOM_CONFIG
|
||||
case constants.Cyberghost:
|
||||
err = settings.readCyberghost(r)
|
||||
case constants.PrivateInternetAccess:
|
||||
settings.EncPreset, err = getPIAEncryptionPreset(r)
|
||||
case constants.VPNUnlimited:
|
||||
err = settings.readVPNUnlimited(r)
|
||||
case constants.Wevpn:
|
||||
err = settings.readWevpn(r)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -176,10 +178,11 @@ func (settings *OpenVPN) read(r reader, serviceProvider string) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func readProtocol(env params.Interface) (tcp bool, err error) {
|
||||
protocol, err := env.Inside("PROTOCOL", []string{constants.TCP, constants.UDP}, params.Default(constants.UDP))
|
||||
func readOpenVPNProtocol(r reader) (tcp bool, err error) {
|
||||
protocol, err := r.env.Inside("OPENVPN_PROTOCOL", []string{constants.TCP, constants.UDP},
|
||||
params.Default(constants.UDP), params.RetroKeys([]string{"PROTOCOL"}, r.onRetroActive))
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("environment variable PROTOCOL: %w", err)
|
||||
return false, fmt.Errorf("environment variable OPENVPN_PROTOCOL: %w", err)
|
||||
}
|
||||
return protocol == constants.TCP, nil
|
||||
}
|
||||
|
||||
@@ -11,8 +11,9 @@ import (
|
||||
func Test_OpenVPN_JSON(t *testing.T) {
|
||||
t.Parallel()
|
||||
in := OpenVPN{
|
||||
Root: true,
|
||||
Flags: []string{},
|
||||
Root: true,
|
||||
Flags: []string{},
|
||||
Ciphers: []string{},
|
||||
}
|
||||
data, err := json.MarshalIndent(in, "", " ")
|
||||
require.NoError(t, err)
|
||||
@@ -23,9 +24,9 @@ func Test_OpenVPN_JSON(t *testing.T) {
|
||||
"flags": [],
|
||||
"mssfix": 0,
|
||||
"run_as_root": true,
|
||||
"cipher": "",
|
||||
"ciphers": [],
|
||||
"auth": "",
|
||||
"custom_config": "",
|
||||
"conf_file": "",
|
||||
"version": "",
|
||||
"encryption_preset": "",
|
||||
"ipv6": false,
|
||||
|
||||
43
internal/configuration/perfectprivacy.go
Normal file
43
internal/configuration/perfectprivacy.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package configuration
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
)
|
||||
|
||||
func (settings *Provider) readPerfectPrivacy(r reader) (err error) {
|
||||
settings.Name = constants.Perfectprivacy
|
||||
servers := r.servers.GetPerfectprivacy()
|
||||
|
||||
settings.ServerSelection.TargetIP, err = readTargetIP(r.env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.ServerSelection.Cities, err = r.env.CSVInside("CITY", constants.PerfectprivacyCityChoices(servers))
|
||||
if err != nil {
|
||||
return fmt.Errorf("environment variable CITY: %w", err)
|
||||
}
|
||||
|
||||
return settings.ServerSelection.OpenVPN.readPerfectPrivacy(r)
|
||||
}
|
||||
|
||||
func (settings *OpenVPNSelection) readPerfectPrivacy(r reader) (err error) {
|
||||
settings.TCP, err = readOpenVPNProtocol(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
portValidation := openvpnPortValidation{
|
||||
tcp: settings.TCP,
|
||||
allowedTCP: []uint16{44, 443, 4433},
|
||||
allowedUDP: []uint16{44, 443, 4433},
|
||||
}
|
||||
settings.CustomPort, err = readOpenVPNCustomPort(r, portValidation)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -26,7 +26,7 @@ func (settings *Provider) readPrivateInternetAccess(r reader) (err error) {
|
||||
return fmt.Errorf("environment variable SERVER_HOSTNAME: %w", err)
|
||||
}
|
||||
|
||||
settings.ServerSelection.Hostnames, err = r.env.CSVInside("SERVER_NAME", constants.PIANameChoices(servers))
|
||||
settings.ServerSelection.Names, err = r.env.CSVInside("SERVER_NAME", constants.PIANameChoices(servers))
|
||||
if err != nil {
|
||||
return fmt.Errorf("environment variable SERVER_NAME: %w", err)
|
||||
}
|
||||
@@ -53,9 +53,9 @@ func (settings *OpenVPNSelection) readPrivateInternetAccess(r reader) (err error
|
||||
return err
|
||||
}
|
||||
|
||||
settings.CustomPort, err = readPortOrZero(r.env, "PORT")
|
||||
settings.CustomPort, err = readOpenVPNCustomPort(r, openvpnPortValidation{allAllowed: true})
|
||||
if err != nil {
|
||||
return fmt.Errorf("environment variable PORT: %w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -65,7 +65,7 @@ func getPIAEncryptionPreset(r reader) (encryptionPreset string, err error) {
|
||||
encryptionPreset, err = r.env.Inside("PIA_ENCRYPTION",
|
||||
[]string{constants.PIAEncryptionPresetNone, constants.PIAEncryptionPresetNormal, constants.PIAEncryptionPresetStrong},
|
||||
params.RetroKeys([]string{"ENCRYPTION"}, r.onRetroActive),
|
||||
params.Default(constants.PIACertificateStrong),
|
||||
params.Default(constants.PIAEncryptionPresetStrong),
|
||||
)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("environment variable PIA_ENCRYPTION: %w", err)
|
||||
|
||||
@@ -31,5 +31,5 @@ func (settings *Provider) readPrivatevpn(r reader) (err error) {
|
||||
return fmt.Errorf("environment variable SERVER_HOSTNAME: %w", err)
|
||||
}
|
||||
|
||||
return settings.ServerSelection.OpenVPN.readProtocolAndPort(r.env)
|
||||
return settings.ServerSelection.OpenVPN.readProtocolAndPort(r)
|
||||
}
|
||||
|
||||
@@ -47,5 +47,5 @@ func (settings *Provider) readProtonvpn(r reader) (err error) {
|
||||
return fmt.Errorf("environment variable FREE_ONLY: %w", err)
|
||||
}
|
||||
|
||||
return settings.ServerSelection.OpenVPN.readProtocolAndPort(r.env)
|
||||
return settings.ServerSelection.OpenVPN.readProtocolAndPort(r)
|
||||
}
|
||||
|
||||
@@ -49,8 +49,12 @@ func (settings *Provider) read(r reader, vpnType string) error {
|
||||
}
|
||||
|
||||
switch settings.Name {
|
||||
case constants.Custom:
|
||||
err = settings.readCustom(r, vpnType)
|
||||
case constants.Cyberghost:
|
||||
err = settings.readCyberghost(r)
|
||||
case constants.Expressvpn:
|
||||
err = settings.readExpressvpn(r)
|
||||
case constants.Fastestvpn:
|
||||
err = settings.readFastestvpn(r)
|
||||
case constants.HideMyAss:
|
||||
@@ -63,6 +67,8 @@ func (settings *Provider) read(r reader, vpnType string) error {
|
||||
err = settings.readMullvad(r)
|
||||
case constants.Nordvpn:
|
||||
err = settings.readNordvpn(r)
|
||||
case constants.Perfectprivacy:
|
||||
err = settings.readPerfectPrivacy(r)
|
||||
case constants.Privado:
|
||||
err = settings.readPrivado(r)
|
||||
case constants.PrivateInternetAccess:
|
||||
@@ -81,6 +87,8 @@ func (settings *Provider) read(r reader, vpnType string) error {
|
||||
err = settings.readVPNUnlimited(r)
|
||||
case constants.Vyprvpn:
|
||||
err = settings.readVyprvpn(r)
|
||||
case constants.Wevpn:
|
||||
err = settings.readWevpn(r)
|
||||
case constants.Windscribe:
|
||||
err = settings.readWindscribe(r)
|
||||
default:
|
||||
@@ -99,12 +107,17 @@ func (settings *Provider) readVPNServiceProvider(r reader, vpnType string) (err
|
||||
switch vpnType {
|
||||
case constants.OpenVPN:
|
||||
allowedVPNServiceProviders = []string{
|
||||
"cyberghost", "fastestvpn", "hidemyass", "ipvanish", "ivpn", "mullvad", "nordvpn",
|
||||
"privado", "pia", "private internet access", "privatevpn", "protonvpn",
|
||||
"purevpn", "surfshark", "torguard", constants.VPNUnlimited, "vyprvpn", "windscribe"}
|
||||
constants.Custom,
|
||||
"cyberghost", constants.Expressvpn, "fastestvpn", "hidemyass", "ipvanish",
|
||||
"ivpn", "mullvad", "nordvpn",
|
||||
constants.Perfectprivacy, "privado", "pia", "private internet access", "privatevpn", "protonvpn",
|
||||
"purevpn", "surfshark", "torguard", constants.VPNUnlimited, "vyprvpn",
|
||||
constants.Wevpn, "windscribe"}
|
||||
case constants.Wireguard:
|
||||
allowedVPNServiceProviders = []string{constants.Mullvad, constants.Windscribe,
|
||||
constants.Ivpn}
|
||||
allowedVPNServiceProviders = []string{
|
||||
constants.Custom, constants.Ivpn,
|
||||
constants.Mullvad, constants.Windscribe,
|
||||
}
|
||||
}
|
||||
|
||||
vpnsp, err := r.env.Inside("VPNSP", allowedVPNServiceProviders,
|
||||
@@ -115,6 +128,11 @@ func (settings *Provider) readVPNServiceProvider(r reader, vpnType string) (err
|
||||
if vpnsp == "pia" { // retro compatibility
|
||||
vpnsp = "private internet access"
|
||||
}
|
||||
|
||||
if settings.isOpenVPNCustomConfig(r.env) { // retro compatibility
|
||||
vpnsp = constants.Custom
|
||||
}
|
||||
|
||||
settings.Name = vpnsp
|
||||
|
||||
return nil
|
||||
@@ -139,42 +157,62 @@ func readTargetIP(env params.Interface) (targetIP net.IP, err error) {
|
||||
return targetIP, nil
|
||||
}
|
||||
|
||||
func readOpenVPNCustomPort(env params.Interface, tcp bool,
|
||||
allowedTCP, allowedUDP []uint16) (port uint16, err error) {
|
||||
port, err = readPortOrZero(env, "PORT")
|
||||
type openvpnPortValidation struct {
|
||||
allAllowed bool
|
||||
tcp bool
|
||||
allowedTCP []uint16
|
||||
allowedUDP []uint16
|
||||
}
|
||||
|
||||
func readOpenVPNCustomPort(r reader, validation openvpnPortValidation) (
|
||||
port uint16, err error) {
|
||||
port, err = readPortOrZero(r.env, "OPENVPN_PORT")
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("environment variable PORT: %w", err)
|
||||
return 0, fmt.Errorf("environment variable OPENVPN_PORT: %w", err)
|
||||
} else if port == 0 {
|
||||
return 0, nil
|
||||
// Try using old variable name
|
||||
port, err = readPortOrZero(r.env, "PORT")
|
||||
if err != nil {
|
||||
r.onRetroActive("PORT", "OPENVPN_PORT")
|
||||
return 0, fmt.Errorf("environment variable PORT: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if tcp {
|
||||
for i := range allowedTCP {
|
||||
if allowedTCP[i] == port {
|
||||
if port == 0 || validation.allAllowed {
|
||||
return port, nil
|
||||
}
|
||||
|
||||
if validation.tcp {
|
||||
for _, allowedPort := range validation.allowedTCP {
|
||||
if port == allowedPort {
|
||||
return port, nil
|
||||
}
|
||||
}
|
||||
return 0, fmt.Errorf(
|
||||
"environment variable PORT: %w: port %d for TCP protocol, can only be one of %s",
|
||||
ErrInvalidPort, port, portsToString(allowedTCP))
|
||||
ErrInvalidPort, port, portsToString(validation.allowedTCP))
|
||||
}
|
||||
for i := range allowedUDP {
|
||||
if allowedUDP[i] == port {
|
||||
for _, allowedPort := range validation.allowedUDP {
|
||||
if port == allowedPort {
|
||||
return port, nil
|
||||
}
|
||||
}
|
||||
return 0, fmt.Errorf(
|
||||
"environment variable PORT: %w: port %d for UDP protocol, can only be one of %s",
|
||||
ErrInvalidPort, port, portsToString(allowedUDP))
|
||||
ErrInvalidPort, port, portsToString(validation.allowedUDP))
|
||||
}
|
||||
|
||||
// note: set allowed to an empty slice to allow all valid ports
|
||||
func readWireguardCustomPort(env params.Interface, allowed []uint16) (port uint16, err error) {
|
||||
port, err = readPortOrZero(env, "WIREGUARD_PORT")
|
||||
port, err = readPortOrZero(env, "WIREGUARD_ENDPOINT_PORT")
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("environment variable WIREGUARD_PORT: %w", err)
|
||||
return 0, fmt.Errorf("environment variable WIREGUARD_ENDPOINT_PORT: %w", err)
|
||||
} else if port == 0 {
|
||||
return 0, nil
|
||||
port, _ = readPortOrZero(env, "WIREGUARD_PORT")
|
||||
if err == nil {
|
||||
return port, nil // 0 or WIREGUARD_PORT value
|
||||
}
|
||||
return 0, nil // default 0
|
||||
}
|
||||
|
||||
if len(allowed) == 0 {
|
||||
@@ -199,3 +237,12 @@ func portsToString(ports []uint16) string {
|
||||
}
|
||||
return strings.Join(slice, ", ")
|
||||
}
|
||||
|
||||
// isOpenVPNCustomConfig is for retro compatibility to set VPNSP=custom
|
||||
// if OPENVPN_CUSTOM_CONFIG is set.
|
||||
func (settings Provider) isOpenVPNCustomConfig(env params.Interface) (ok bool) {
|
||||
s, _ := env.Get("VPN_TYPE")
|
||||
isOpenVPN := s == constants.OpenVPN
|
||||
s, _ = env.Get("OPENVPN_CUSTOM_CONFIG")
|
||||
return isOpenVPN && s != ""
|
||||
}
|
||||
|
||||
@@ -24,15 +24,32 @@ func Test_Provider_lines(t *testing.T) {
|
||||
settings: Provider{
|
||||
Name: constants.Cyberghost,
|
||||
ServerSelection: ServerSelection{
|
||||
VPN: constants.OpenVPN,
|
||||
Groups: []string{"group"},
|
||||
Regions: []string{"a", "El country"},
|
||||
VPN: constants.OpenVPN,
|
||||
Countries: []string{"a", "El country"},
|
||||
},
|
||||
},
|
||||
lines: []string{
|
||||
"|--Cyberghost settings:",
|
||||
" |--Server groups: group",
|
||||
" |--Regions: a, El country",
|
||||
" |--Countries: a, El country",
|
||||
" |--OpenVPN selection:",
|
||||
" |--Protocol: udp",
|
||||
},
|
||||
},
|
||||
"expressvpn": {
|
||||
settings: Provider{
|
||||
Name: constants.Expressvpn,
|
||||
ServerSelection: ServerSelection{
|
||||
VPN: constants.OpenVPN,
|
||||
Hostnames: []string{"a", "b"},
|
||||
Countries: []string{"c", "d"},
|
||||
Cities: []string{"e", "f"},
|
||||
},
|
||||
},
|
||||
lines: []string{
|
||||
"|--Expressvpn settings:",
|
||||
" |--Countries: c, d",
|
||||
" |--Cities: e, f",
|
||||
" |--Hostnames: a, b",
|
||||
" |--OpenVPN selection:",
|
||||
" |--Protocol: udp",
|
||||
},
|
||||
@@ -151,6 +168,21 @@ func Test_Provider_lines(t *testing.T) {
|
||||
" |--Protocol: udp",
|
||||
},
|
||||
},
|
||||
"perfectprivacy": {
|
||||
settings: Provider{
|
||||
Name: constants.Perfectprivacy,
|
||||
ServerSelection: ServerSelection{
|
||||
VPN: constants.OpenVPN,
|
||||
Cities: []string{"a", "b"},
|
||||
},
|
||||
},
|
||||
lines: []string{
|
||||
"|--Perfect Privacy settings:",
|
||||
" |--Cities: a, b",
|
||||
" |--OpenVPN selection:",
|
||||
" |--Protocol: udp",
|
||||
},
|
||||
},
|
||||
"privado": {
|
||||
settings: Provider{
|
||||
Name: constants.Privado,
|
||||
@@ -324,6 +356,27 @@ func Test_Provider_lines(t *testing.T) {
|
||||
" |--Protocol: udp",
|
||||
},
|
||||
},
|
||||
"wevpn": {
|
||||
settings: Provider{
|
||||
Name: constants.Wevpn,
|
||||
ServerSelection: ServerSelection{
|
||||
VPN: constants.OpenVPN,
|
||||
Cities: []string{"a", "b"},
|
||||
Hostnames: []string{"c", "d"},
|
||||
OpenVPN: OpenVPNSelection{
|
||||
CustomPort: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
lines: []string{
|
||||
"|--Wevpn settings:",
|
||||
" |--Cities: a, b",
|
||||
" |--Hostnames: c, d",
|
||||
" |--OpenVPN selection:",
|
||||
" |--Protocol: udp",
|
||||
" |--Custom port: 1",
|
||||
},
|
||||
},
|
||||
"windscribe": {
|
||||
settings: Provider{
|
||||
Name: constants.Windscribe,
|
||||
@@ -372,7 +425,7 @@ func Test_readProtocol(t *testing.T) {
|
||||
}{
|
||||
"error": {
|
||||
mockErr: errDummy,
|
||||
err: errors.New("environment variable PROTOCOL: dummy"),
|
||||
err: errors.New("environment variable OPENVPN_PROTOCOL: dummy"),
|
||||
},
|
||||
"success": {
|
||||
mockStr: "tcp",
|
||||
@@ -388,10 +441,13 @@ func Test_readProtocol(t *testing.T) {
|
||||
|
||||
env := mock_params.NewMockInterface(ctrl)
|
||||
env.EXPECT().
|
||||
Inside("PROTOCOL", []string{"tcp", "udp"}, gomock.Any()).
|
||||
Inside("OPENVPN_PROTOCOL", []string{"tcp", "udp"}, gomock.Any(), gomock.Any()).
|
||||
Return(testCase.mockStr, testCase.mockErr)
|
||||
reader := reader{
|
||||
env: env,
|
||||
}
|
||||
|
||||
tcp, err := readProtocol(env)
|
||||
tcp, err := readOpenVPNProtocol(reader)
|
||||
|
||||
if testCase.err != nil {
|
||||
require.Error(t, err)
|
||||
|
||||
@@ -35,5 +35,5 @@ func (settings *Provider) readPurevpn(r reader) (err error) {
|
||||
return fmt.Errorf("environment variable SERVER_HOSTNAME: %w", err)
|
||||
}
|
||||
|
||||
return settings.ServerSelection.OpenVPN.readProtocolOnly(r.env)
|
||||
return settings.ServerSelection.OpenVPN.readProtocolOnly(r)
|
||||
}
|
||||
|
||||
@@ -8,30 +8,38 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/qdm12/golibs/logging"
|
||||
ovpnextract "github.com/qdm12/gluetun/internal/openvpn/extract"
|
||||
"github.com/qdm12/golibs/params"
|
||||
"github.com/qdm12/golibs/verification"
|
||||
)
|
||||
|
||||
//go:generate mockgen -destination=warner_mock_test.go -package configuration . Warner
|
||||
|
||||
type reader struct {
|
||||
servers models.AllServers
|
||||
env params.Interface
|
||||
logger logging.Logger
|
||||
warner Warner
|
||||
regex verification.Regex
|
||||
ovpnExt ovpnextract.Interface
|
||||
}
|
||||
|
||||
type Warner interface {
|
||||
Warn(s string)
|
||||
}
|
||||
|
||||
func newReader(env params.Interface,
|
||||
servers models.AllServers, logger logging.Logger) reader {
|
||||
servers models.AllServers, warner Warner) reader {
|
||||
return reader{
|
||||
servers: servers,
|
||||
env: env,
|
||||
logger: logger,
|
||||
warner: warner,
|
||||
regex: verification.NewRegex(),
|
||||
ovpnExt: ovpnextract.New(),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *reader) onRetroActive(oldKey, newKey string) {
|
||||
r.logger.Warn(
|
||||
r.warner.Warn(
|
||||
"You are using the old environment variable " + oldKey +
|
||||
", please consider changing it to " + newKey)
|
||||
}
|
||||
|
||||
@@ -51,22 +51,22 @@ func (r *reader) getFromEnvOrSecretFile(envKey string, compulsory bool, retroKey
|
||||
file, fileErr := os.OpenFile(filepath, os.O_RDONLY, 0)
|
||||
if os.IsNotExist(fileErr) {
|
||||
if compulsory {
|
||||
return "", envErr
|
||||
return "", fmt.Errorf("environment variable %s: %w", envKey, envErr)
|
||||
}
|
||||
return "", nil
|
||||
} else if fileErr != nil {
|
||||
return "", fmt.Errorf("%w: %s", ErrReadSecretFile, fileErr)
|
||||
return "", fmt.Errorf("%w: %s: %s", ErrReadSecretFile, filepath, fileErr)
|
||||
}
|
||||
|
||||
b, err := io.ReadAll(file)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("%w: %s", ErrReadSecretFile, err)
|
||||
return "", fmt.Errorf("%w: %s: %s", ErrReadSecretFile, filepath, err)
|
||||
}
|
||||
|
||||
value = string(b)
|
||||
value = cleanSuffix(value)
|
||||
if compulsory && value == "" {
|
||||
return "", ErrSecretFileIsEmpty
|
||||
return "", fmt.Errorf("%s: %w", filepath, ErrSecretFileIsEmpty)
|
||||
}
|
||||
|
||||
return value, nil
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"net"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/golibs/params"
|
||||
)
|
||||
|
||||
type ServerSelection struct { //nolint:maligned
|
||||
@@ -16,14 +15,12 @@ type ServerSelection struct { //nolint:maligned
|
||||
// Cyberghost, PIA, Protonvpn, Surfshark, Windscribe, Vyprvpn, NordVPN
|
||||
Regions []string `json:"regions"`
|
||||
|
||||
// Cyberghost
|
||||
Groups []string `json:"groups"`
|
||||
|
||||
// Fastestvpn, HideMyAss, IPVanish, IVPN, Mullvad, PrivateVPN, Protonvpn, PureVPN, VPNUnlimited
|
||||
// Expressvpn, Fastestvpn, HideMyAss, IPVanish, IVPN, Mullvad, PrivateVPN, Protonvpn, PureVPN, VPNUnlimited
|
||||
Countries []string `json:"countries"`
|
||||
// HideMyAss, IPVanish, IVPN, Mullvad, PrivateVPN, Protonvpn, PureVPN, VPNUnlimited, Windscribe
|
||||
// Expressvpn, HideMyAss, IPVanish, IVPN, Mullvad, Perfectprivacy, PrivateVPN, Protonvpn,
|
||||
// PureVPN, VPNUnlimited, WeVPN, Windscribe
|
||||
Cities []string `json:"cities"`
|
||||
// Fastestvpn, HideMyAss, IPVanish, IVPN, PrivateVPN, Windscribe, Privado, Protonvpn, VPNUnlimited
|
||||
// Expressvpn, Fastestvpn, HideMyAss, IPVanish, IVPN, PrivateVPN, Windscribe, Privado, Protonvpn, VPNUnlimited, WeVPN
|
||||
Hostnames []string `json:"hostnames"`
|
||||
Names []string `json:"names"` // Protonvpn
|
||||
|
||||
@@ -52,10 +49,6 @@ func (selection ServerSelection) toLines() (lines []string) {
|
||||
lines = append(lines, lastIndent+"Target IP address: "+selection.TargetIP.String())
|
||||
}
|
||||
|
||||
if len(selection.Groups) > 0 {
|
||||
lines = append(lines, lastIndent+"Server groups: "+commaJoin(selection.Groups))
|
||||
}
|
||||
|
||||
if len(selection.Countries) > 0 {
|
||||
lines = append(lines, lastIndent+"Countries: "+commaJoin(selection.Countries))
|
||||
}
|
||||
@@ -106,14 +99,19 @@ func (selection ServerSelection) toLines() (lines []string) {
|
||||
}
|
||||
|
||||
type OpenVPNSelection struct {
|
||||
ConfFile string `json:"conf_file"` // Custom configuration file path
|
||||
TCP bool `json:"tcp"` // UDP if TCP is false
|
||||
CustomPort uint16 `json:"custom_port"` // HideMyAss, Mullvad, PIA, ProtonVPN, Windscribe
|
||||
CustomPort uint16 `json:"custom_port"` // HideMyAss, Mullvad, PIA, ProtonVPN, WeVPN, Windscribe
|
||||
EncPreset string `json:"encryption_preset"` // PIA - needed to get the port number
|
||||
}
|
||||
|
||||
func (settings *OpenVPNSelection) lines() (lines []string) {
|
||||
lines = append(lines, lastIndent+"OpenVPN selection:")
|
||||
|
||||
if settings.ConfFile != "" {
|
||||
lines = append(lines, indent+lastIndent+"Custom configuration file: "+settings.ConfFile)
|
||||
}
|
||||
|
||||
lines = append(lines, indent+lastIndent+"Protocol: "+protoToString(settings.TCP))
|
||||
|
||||
if settings.CustomPort != 0 {
|
||||
@@ -127,34 +125,52 @@ func (settings *OpenVPNSelection) lines() (lines []string) {
|
||||
return lines
|
||||
}
|
||||
|
||||
func (settings *OpenVPNSelection) readProtocolOnly(env params.Interface) (err error) {
|
||||
settings.TCP, err = readProtocol(env)
|
||||
func (settings *OpenVPNSelection) readProtocolOnly(r reader) (err error) {
|
||||
settings.TCP, err = readOpenVPNProtocol(r)
|
||||
return err
|
||||
}
|
||||
|
||||
func (settings *OpenVPNSelection) readProtocolAndPort(env params.Interface) (err error) {
|
||||
settings.TCP, err = readProtocol(env)
|
||||
func (settings *OpenVPNSelection) readProtocolAndPort(r reader) (err error) {
|
||||
settings.TCP, err = readOpenVPNProtocol(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.CustomPort, err = readPortOrZero(env, "PORT")
|
||||
settings.CustomPort, err = readOpenVPNCustomPort(r, openvpnPortValidation{allAllowed: true})
|
||||
if err != nil {
|
||||
return fmt.Errorf("environment variable PORT: %w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type WireguardSelection struct {
|
||||
CustomPort uint16 `json:"custom_port"` // Mullvad
|
||||
// EndpointPort is a the server port to use for the VPN server.
|
||||
// It is optional for Wireguard VPN providers IVPN, Mullvad
|
||||
// and Windscribe, and compulsory for the others
|
||||
EndpointPort uint16 `json:"port,omitempty"`
|
||||
// PublicKey is the server public key.
|
||||
// It is only used with VPN providers generating Wireguard
|
||||
// configurations specific to each server and user.
|
||||
PublicKey string `json:"publickey,omitempty"`
|
||||
// EndpointIP is the server endpoint IP address.
|
||||
// It is only used with VPN providers generating Wireguard
|
||||
// configurations specific to each server and user.
|
||||
EndpointIP net.IP `json:"endpoint_ip,omitempty"`
|
||||
}
|
||||
|
||||
func (settings *WireguardSelection) lines() (lines []string) {
|
||||
lines = append(lines, lastIndent+"Wireguard selection:")
|
||||
|
||||
if settings.CustomPort != 0 {
|
||||
lines = append(lines, indent+lastIndent+"Custom port: "+fmt.Sprint(settings.CustomPort))
|
||||
if settings.PublicKey != "" {
|
||||
lines = append(lines, indent+lastIndent+"Public key: "+settings.PublicKey)
|
||||
}
|
||||
|
||||
if settings.EndpointIP != nil {
|
||||
endpoint := settings.EndpointIP.String() + ":" + fmt.Sprint(settings.EndpointPort)
|
||||
lines = append(lines, indent+lastIndent+"Server endpoint: "+endpoint)
|
||||
} else if settings.EndpointPort != 0 {
|
||||
lines = append(lines, indent+lastIndent+"Custom port: "+fmt.Sprint(settings.EndpointPort))
|
||||
}
|
||||
|
||||
return lines
|
||||
|
||||
@@ -40,7 +40,7 @@ func (settings *ControlServer) read(r reader) (err error) {
|
||||
settings.Port, warning, err = r.env.ListeningPort(
|
||||
"HTTP_CONTROL_SERVER_PORT", params.Default("8000"))
|
||||
if len(warning) > 0 {
|
||||
r.logger.Warn(warning)
|
||||
r.warner.Warn(warning)
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("environment variable HTTP_CONTROL_SERVER_PORT: %w", err)
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/qdm12/golibs/logging"
|
||||
"github.com/qdm12/golibs/params"
|
||||
)
|
||||
|
||||
@@ -66,8 +65,8 @@ var (
|
||||
// Read obtains all configuration options for the program and returns an error as soon
|
||||
// as an error is encountered reading them.
|
||||
func (settings *Settings) Read(env params.Interface, servers models.AllServers,
|
||||
logger logging.Logger) (err error) {
|
||||
r := newReader(env, servers, logger)
|
||||
warner Warner) (err error) {
|
||||
r := newReader(env, servers, warner)
|
||||
|
||||
settings.VersionInformation, err = r.env.OnOff("VERSION_INFORMATION", params.Default("on"))
|
||||
if err != nil {
|
||||
|
||||
@@ -51,7 +51,8 @@ func Test_Settings_lines(t *testing.T) {
|
||||
" |--Timezone: NOT SET ⚠️ - it can cause time related issues",
|
||||
"|--Health:",
|
||||
" |--Server address: ",
|
||||
" |--OpenVPN:",
|
||||
" |--Address to ping: ",
|
||||
" |--VPN:",
|
||||
" |--Initial duration: 0s",
|
||||
"|--HTTP control server:",
|
||||
" |--Listening port: 0",
|
||||
|
||||
@@ -62,7 +62,7 @@ func (settings *ShadowSocks) read(r reader) (err error) {
|
||||
|
||||
warning, err := settings.getAddress(r.env)
|
||||
if warning != "" {
|
||||
r.logger.Warn(warning)
|
||||
r.warner.Warn(warning)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -50,7 +50,7 @@ func (settings *Provider) readSurfshark(r reader) (err error) {
|
||||
return fmt.Errorf("environment variable MULTIHOP_ONLY: %w", err)
|
||||
}
|
||||
|
||||
return settings.ServerSelection.OpenVPN.readProtocolOnly(r.env)
|
||||
return settings.ServerSelection.OpenVPN.readProtocolOnly(r)
|
||||
}
|
||||
|
||||
func surfsharkRetroRegion(selection ServerSelection) (
|
||||
|
||||
@@ -115,7 +115,7 @@ func Test_Provider_readSurfshark(t *testing.T) {
|
||||
settings: Provider{
|
||||
Name: constants.Surfshark,
|
||||
},
|
||||
err: errors.New("environment variable PROTOCOL: dummy test error"),
|
||||
err: errors.New("environment variable OPENVPN_PROTOCOL: dummy test error"),
|
||||
},
|
||||
"default settings": {
|
||||
targetIP: stringCall{call: true},
|
||||
@@ -214,7 +214,7 @@ func Test_Provider_readSurfshark(t *testing.T) {
|
||||
Return(testCase.multiHop.value, testCase.multiHop.err)
|
||||
}
|
||||
if testCase.protocol.call {
|
||||
env.EXPECT().Inside("PROTOCOL", []string{constants.TCP, constants.UDP}, gomock.Any()).
|
||||
env.EXPECT().Inside("OPENVPN_PROTOCOL", []string{constants.TCP, constants.UDP}, gomock.Any()).
|
||||
Return(testCase.protocol.value, testCase.protocol.err)
|
||||
}
|
||||
|
||||
|
||||
@@ -31,5 +31,5 @@ func (settings *Provider) readTorguard(r reader) (err error) {
|
||||
return fmt.Errorf("environment variable SERVER_HOSTNAME: %w", err)
|
||||
}
|
||||
|
||||
return settings.ServerSelection.OpenVPN.readProtocolAndPort(r.env)
|
||||
return settings.ServerSelection.OpenVPN.readProtocolAndPort(r)
|
||||
}
|
||||
|
||||
@@ -9,25 +9,28 @@ import (
|
||||
)
|
||||
|
||||
type Updater struct {
|
||||
Period time.Duration `json:"period"`
|
||||
DNSAddress string `json:"dns_address"`
|
||||
Cyberghost bool `json:"cyberghost"`
|
||||
Fastestvpn bool `json:"fastestvpn"`
|
||||
HideMyAss bool `json:"hidemyass"`
|
||||
Ipvanish bool `json:"ipvanish"`
|
||||
Ivpn bool `json:"ivpn"`
|
||||
Mullvad bool `json:"mullvad"`
|
||||
Nordvpn bool `json:"nordvpn"`
|
||||
PIA bool `json:"pia"`
|
||||
Privado bool `json:"privado"`
|
||||
Privatevpn bool `json:"privatevpn"`
|
||||
Protonvpn bool `json:"protonvpn"`
|
||||
Purevpn bool `json:"purevpn"`
|
||||
Surfshark bool `json:"surfshark"`
|
||||
Torguard bool `json:"torguard"`
|
||||
VPNUnlimited bool `json:"vpnunlimited"`
|
||||
Vyprvpn bool `json:"vyprvpn"`
|
||||
Windscribe bool `json:"windscribe"`
|
||||
Period time.Duration `json:"period"`
|
||||
DNSAddress string `json:"dns_address"`
|
||||
Cyberghost bool `json:"cyberghost"`
|
||||
Expressvpn bool `json:"expressvpn"`
|
||||
Fastestvpn bool `json:"fastestvpn"`
|
||||
HideMyAss bool `json:"hidemyass"`
|
||||
Ipvanish bool `json:"ipvanish"`
|
||||
Ivpn bool `json:"ivpn"`
|
||||
Mullvad bool `json:"mullvad"`
|
||||
Nordvpn bool `json:"nordvpn"`
|
||||
Perfectprivacy bool `json:"perfectprivacy"`
|
||||
PIA bool `json:"pia"`
|
||||
Privado bool `json:"privado"`
|
||||
Privatevpn bool `json:"privatevpn"`
|
||||
Protonvpn bool `json:"protonvpn"`
|
||||
Purevpn bool `json:"purevpn"`
|
||||
Surfshark bool `json:"surfshark"`
|
||||
Torguard bool `json:"torguard"`
|
||||
VPNUnlimited bool `json:"vpnunlimited"`
|
||||
Vyprvpn bool `json:"vyprvpn"`
|
||||
Wevpn bool `json:"wevpn"`
|
||||
Windscribe bool `json:"windscribe"`
|
||||
// The two below should be used in CLI mode only
|
||||
CLI bool `json:"-"`
|
||||
}
|
||||
@@ -55,6 +58,7 @@ func (settings *Updater) EnableAll() {
|
||||
settings.Ivpn = true
|
||||
settings.Mullvad = true
|
||||
settings.Nordvpn = true
|
||||
settings.Perfectprivacy = true
|
||||
settings.Privado = true
|
||||
settings.PIA = true
|
||||
settings.Privado = true
|
||||
@@ -65,6 +69,7 @@ func (settings *Updater) EnableAll() {
|
||||
settings.Torguard = true
|
||||
settings.VPNUnlimited = true
|
||||
settings.Vyprvpn = true
|
||||
settings.Wevpn = true
|
||||
settings.Windscribe = true
|
||||
}
|
||||
|
||||
|
||||
@@ -58,10 +58,8 @@ func (settings *VPN) read(r reader) (err error) {
|
||||
}
|
||||
settings.Type = vpnType
|
||||
|
||||
if !settings.isOpenVPNCustomConfig(r.env) {
|
||||
if err := settings.Provider.read(r, vpnType); err != nil {
|
||||
return fmt.Errorf("%w: %s", errReadProviderSettings, err)
|
||||
}
|
||||
if err := settings.Provider.read(r, vpnType); err != nil {
|
||||
return fmt.Errorf("%w: %s", errReadProviderSettings, err)
|
||||
}
|
||||
|
||||
switch settings.Type {
|
||||
@@ -79,19 +77,3 @@ func (settings *VPN) read(r reader) (err error) {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (settings VPN) isOpenVPNCustomConfig(env params.Interface) (ok bool) {
|
||||
if settings.Type != constants.OpenVPN {
|
||||
return false
|
||||
}
|
||||
s, err := env.Get("OPENVPN_CUSTOM_CONFIG")
|
||||
return err == nil && s != ""
|
||||
}
|
||||
|
||||
func (settings VPN) VPNInterface() (intf string) {
|
||||
if settings.Type == constants.Wireguard {
|
||||
return settings.Wireguard.Interface
|
||||
}
|
||||
// OpenVPN
|
||||
return settings.OpenVPN.Interface
|
||||
}
|
||||
|
||||
@@ -42,18 +42,18 @@ func (settings *Provider) readVPNUnlimited(r reader) (err error) {
|
||||
return fmt.Errorf("environment variable STREAM_ONLY: %w", err)
|
||||
}
|
||||
|
||||
return settings.ServerSelection.OpenVPN.readProtocolOnly(r.env)
|
||||
return settings.ServerSelection.OpenVPN.readProtocolOnly(r)
|
||||
}
|
||||
|
||||
func (settings *OpenVPN) readVPNUnlimited(r reader) (err error) {
|
||||
settings.ClientKey, err = readClientKey(r)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("%w: %s", errClientKey, err)
|
||||
}
|
||||
|
||||
settings.ClientCrt, err = readClientCertificate(r)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("%w: %s", errClientCert, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
46
internal/configuration/warner_mock_test.go
Normal file
46
internal/configuration/warner_mock_test.go
Normal file
@@ -0,0 +1,46 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/qdm12/gluetun/internal/configuration (interfaces: Warner)
|
||||
|
||||
// Package configuration is a generated GoMock package.
|
||||
package configuration
|
||||
|
||||
import (
|
||||
reflect "reflect"
|
||||
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
)
|
||||
|
||||
// MockWarner is a mock of Warner interface.
|
||||
type MockWarner struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockWarnerMockRecorder
|
||||
}
|
||||
|
||||
// MockWarnerMockRecorder is the mock recorder for MockWarner.
|
||||
type MockWarnerMockRecorder struct {
|
||||
mock *MockWarner
|
||||
}
|
||||
|
||||
// NewMockWarner creates a new mock instance.
|
||||
func NewMockWarner(ctrl *gomock.Controller) *MockWarner {
|
||||
mock := &MockWarner{ctrl: ctrl}
|
||||
mock.recorder = &MockWarnerMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockWarner) EXPECT() *MockWarnerMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// Warn mocks base method.
|
||||
func (m *MockWarner) Warn(arg0 string) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "Warn", arg0)
|
||||
}
|
||||
|
||||
// Warn indicates an expected call of Warn.
|
||||
func (mr *MockWarnerMockRecorder) Warn(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Warn", reflect.TypeOf((*MockWarner)(nil).Warn), arg0)
|
||||
}
|
||||
57
internal/configuration/wevpn.go
Normal file
57
internal/configuration/wevpn.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package configuration
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
)
|
||||
|
||||
func (settings *Provider) readWevpn(r reader) (err error) {
|
||||
settings.Name = constants.Wevpn
|
||||
servers := r.servers.GetWevpn()
|
||||
|
||||
settings.ServerSelection.TargetIP, err = readTargetIP(r.env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.ServerSelection.Cities, err = r.env.CSVInside("CITY", constants.WevpnCityChoices(servers))
|
||||
if err != nil {
|
||||
return fmt.Errorf("environment variable CITY: %w", err)
|
||||
}
|
||||
|
||||
settings.ServerSelection.Hostnames, err = r.env.CSVInside("SERVER_HOSTNAME", constants.WevpnHostnameChoices(servers))
|
||||
if err != nil {
|
||||
return fmt.Errorf("environment variable SERVER_HOSTNAME: %w", err)
|
||||
}
|
||||
|
||||
return settings.ServerSelection.OpenVPN.readWevpn(r)
|
||||
}
|
||||
|
||||
func (settings *OpenVPNSelection) readWevpn(r reader) (err error) {
|
||||
settings.TCP, err = readOpenVPNProtocol(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
validation := openvpnPortValidation{
|
||||
tcp: settings.TCP,
|
||||
allowedTCP: []uint16{53, 1195, 1199, 2018},
|
||||
allowedUDP: []uint16{80, 1194, 1198},
|
||||
}
|
||||
settings.CustomPort, err = readOpenVPNCustomPort(r, validation)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (settings *OpenVPN) readWevpn(r reader) (err error) {
|
||||
settings.ClientKey, err = readClientKey(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -32,7 +32,7 @@ func (settings *Provider) readWindscribe(r reader) (err error) {
|
||||
return fmt.Errorf("environment variable SERVER_HOSTNAME: %w", err)
|
||||
}
|
||||
|
||||
err = settings.ServerSelection.OpenVPN.readWindscribe(r.env)
|
||||
err = settings.ServerSelection.OpenVPN.readWindscribe(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -40,15 +40,17 @@ func (settings *Provider) readWindscribe(r reader) (err error) {
|
||||
return settings.ServerSelection.Wireguard.readWindscribe(r.env)
|
||||
}
|
||||
|
||||
func (settings *OpenVPNSelection) readWindscribe(env params.Interface) (err error) {
|
||||
settings.TCP, err = readProtocol(env)
|
||||
func (settings *OpenVPNSelection) readWindscribe(r reader) (err error) {
|
||||
settings.TCP, err = readOpenVPNProtocol(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.CustomPort, err = readOpenVPNCustomPort(env, settings.TCP,
|
||||
[]uint16{21, 22, 80, 123, 143, 443, 587, 1194, 3306, 8080, 54783},
|
||||
[]uint16{53, 80, 123, 443, 1194, 54783})
|
||||
settings.CustomPort, err = readOpenVPNCustomPort(r, openvpnPortValidation{
|
||||
tcp: settings.TCP,
|
||||
allowedTCP: []uint16{21, 22, 80, 123, 143, 443, 587, 1194, 3306, 8080, 54783},
|
||||
allowedUDP: []uint16{53, 80, 123, 443, 1194, 54783},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -57,7 +59,7 @@ func (settings *OpenVPNSelection) readWindscribe(env params.Interface) (err erro
|
||||
}
|
||||
|
||||
func (settings *WireguardSelection) readWindscribe(env params.Interface) (err error) {
|
||||
settings.CustomPort, err = readWireguardCustomPort(env,
|
||||
settings.EndpointPort, err = readWireguardCustomPort(env,
|
||||
[]uint16{53, 80, 123, 443, 1194, 65142})
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package constants
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
)
|
||||
|
||||
@@ -11,31 +9,14 @@ const (
|
||||
CyberghostCertificate = "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"
|
||||
)
|
||||
|
||||
func CyberghostRegionChoices(servers []models.CyberghostServer) (choices []string) {
|
||||
func CyberghostCountryChoices(servers []models.CyberghostServer) (choices []string) {
|
||||
choices = make([]string, len(servers))
|
||||
for i := range servers {
|
||||
choices[i] = servers[i].Region
|
||||
choices[i] = servers[i].Country
|
||||
}
|
||||
return makeUnique(choices)
|
||||
}
|
||||
|
||||
func CyberghostGroupChoices(servers []models.CyberghostServer) (choices []string) {
|
||||
uniqueChoices := map[string]struct{}{}
|
||||
for _, server := range servers {
|
||||
uniqueChoices[server.Group] = struct{}{}
|
||||
}
|
||||
|
||||
choices = make([]string, 0, len(uniqueChoices))
|
||||
for choice := range uniqueChoices {
|
||||
choices = append(choices, choice)
|
||||
}
|
||||
|
||||
sortable := sort.StringSlice(choices)
|
||||
sortable.Sort()
|
||||
|
||||
return sortable
|
||||
}
|
||||
|
||||
func CyberghostHostnameChoices(servers []models.CyberghostServer) (choices []string) {
|
||||
choices = make([]string, len(servers))
|
||||
for i := range servers {
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
package constants
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/qdm12/gluetun/internal/storage"
|
||||
"github.com/qdm12/golibs/logging/mock_logging"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_CyberghostGroupChoices(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctrl := gomock.NewController(t)
|
||||
|
||||
logger := mock_logging.NewMockLogger(ctrl)
|
||||
logger.EXPECT().Info(gomock.Any())
|
||||
|
||||
storage, err := storage.New(logger, "")
|
||||
require.NoError(t, err)
|
||||
|
||||
servers := storage.GetServers()
|
||||
|
||||
expected := []string{"Premium TCP Asia", "Premium TCP Europe",
|
||||
"Premium TCP USA", "Premium UDP Asia", "Premium UDP Europe",
|
||||
"Premium UDP USA"}
|
||||
choices := CyberghostGroupChoices(servers.GetCyberghost())
|
||||
|
||||
assert.Equal(t, expected, choices)
|
||||
}
|
||||
37
internal/constants/expressvpn.go
Normal file
37
internal/constants/expressvpn.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package constants
|
||||
|
||||
import (
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
)
|
||||
|
||||
//nolint:lll
|
||||
const (
|
||||
ExpressvpnCert = "MIIDTjCCAregAwIBAgIDKzZvMA0GCSqGSIb3DQEBCwUAMIGFMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFTATBgNVBAcTDFNhbkZyYW5jaXNjbzEVMBMGA1UEChMMRm9ydC1GdW5zdG9uMRgwFgYDVQQDEw9Gb3J0LUZ1bnN0b24gQ0ExITAfBgkqhkiG9w0BCQEWEm1lQG15aG9zdC5teWRvbWFpbjAgFw0xNjExMDMwMzA2MThaGA8yMDY2MTEwMzAzMDYxOFowgYoxCzAJBgNVBAYTAlZHMQwwCgYDVQQIDANCVkkxEzARBgNVBAoMCkV4cHJlc3NWUE4xEzARBgNVBAsMCkV4cHJlc3NWUE4xHDAaBgNVBAMME2V4cHJlc3N2cG5fY3VzdG9tZXIxJTAjBgkqhkiG9w0BCQEWFnN1cHBvcnRAZXhwcmVzc3Zwbi5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCrOYt/KOi2uMDGev3pXg8j1SO4J/4EVWDF7vJcKr2jrZlqD/zuAFx2W1YWvwumPO6PKH4PU9621aNdiumaUkv/RplCfznnnxqobhJuTE2oA+rS1bOq+9OhHwF9jgNXNVk+XX4d0toST5uGE6Z3OdmPBur8o5AlCf78PDSAwpFOw5HrgLqOEU4hTweC1/czX2VsvsHv22HRI6JMZgP8gGQii/p9iukqfaJvGdPciL5p1QRBUQIi8P8pNvEp1pVIpxYj7/LOUqb2DxFvgmp2v1IQ0Yu88SWsFk84+xAYHzfkLyS31Sqj5uLRBnJqx3fIlOihQ50GI72fwPMwo+OippvVAgMBAAGjPzA9MAwGA1UdEwEB/wQCMAAwDgYDVR0PAQH/BAQDAgSwMB0GA1UdDgQWBBSkBM1TCX9kBgFsv2RmOzudMXa9njANBgkqhkiG9w0BAQsFAAOBgQA+2e4b+33zFmA+1ZQ46kWkfiB+fEeDyMwMLeYYyDS2d8mZhNZKdOw7dy4Ifz9Vqzp4aKuQ6j61c6k1UaQQL0tskqWVzslSFvs9NZyUAJLLdGUc5TT2MiLwiXQwd4UvH6bGeePdhvB4+ZbW7VMD7TE8hZhjhAL4F6yAP1EQvg3LDA=="
|
||||
ExpressvpnRSAKey = "MIIEpAIBAAKCAQEAqzmLfyjotrjAxnr96V4PI9UjuCf+BFVgxe7yXCq9o62Zag/87gBcdltWFr8Lpjzujyh+D1PettWjXYrpmlJL/0aZQn85558aqG4SbkxNqAPq0tWzqvvToR8BfY4DVzVZPl1+HdLaEk+bhhOmdznZjwbq/KOQJQn+/Dw0gMKRTsOR64C6jhFOIU8Hgtf3M19lbL7B79th0SOiTGYD/IBkIov6fYrpKn2ibxnT3Ii+adUEQVECIvD/KTbxKdaVSKcWI+/yzlKm9g8Rb4Jqdr9SENGLvPElrBZPOPsQGB835C8kt9Uqo+bi0QZyasd3yJTooUOdBiO9n8DzMKPjoqab1QIDAQABAoIBAHgsekC0SKi+AOcNOZqJ3pxqophE0V7fQX2KWGXhxZnUZMFxGTc936deMYzjZ1y0lUa6x8cgOUcfqHol3hDmw9oWBckLHGv5Wi9umdb6DOLoZO62+FQATSdfaJ9jheq2Ub2YxsRN0apaXzB6KDKz0oM0+sZ4Udn9Kw6DfuIELRIWwEx4w0v3gKW7YLC4Jkc4AwLkPK03xEA/qImfkCmaMPLhrgVQt+IFfP8bXzL7CCC04rNU/IS8pyjex+iUolnQZlbXntF7Bm4V2mz0827ZVqrgAb/hEQRlsTW3rRkVh+rrdoUE7BCZRTFmRCbLoShjN6XuSf4sAus8ch4UEN12gN0CgYEA4o/tSvij1iPaXLmt4KOEuxqmSGB8MLKhFde8lBbNdrDgxiIH9bH7khKx15XRTX0qLDbs8b2/UJygZG0Aa1kIBqZTXTgeMAuxPRTesALJPdqQ/ROnbJcdFkI7gllrAG8VB0fH4wTRsRd0vWEB6YlCdE107u6LEsLAHxOj9Q5819cCgYEAwXjx9RkQ2qITBx5Ewib8YsltA0n3cmRomPicLlsnKV5DfvyCLpFIsZ1h3f9dUpfxRLwzp8wcoLiq9cCoOGdu1udw/yBTqmhaXWhUK/g77f9Ze2ZB1OEhuyKLYJ1vW/h/Z/a1aPCMxZqsDTPCePsuO8Cez5gqs8LjM3W7EyzRxDMCgYEAvhHrDFt975fSiLoJgo0MPIAGAnBXn+8sLwv3m/FpW+rWF8LTFK/Fku12H5wDpNOdvswxijkauIE+GiJMGMLvdcyx4WHECaC1h73reJRNykOEIZ0Md5BrCZJ1JEzp9Mo8RQhWTEFtvfkkqgApP4g0pSeaMx0StaGG1kt+4IbP+68CgYBrZdQKlquAck/Vt7u7eyDHRcE5/ilaWtqlb/xizz7h++3D5C/v4b5UumTFcyg+3RGVclPKZcfOgDSGzzeSd/hTW46iUTOgeOUQzQVMkzPRXdoyYgVRQtgSpY5xR3O1vjAbahwx8LZ0SvQPMBhYSDbV/Isr+fBacWjl/AipEEwxeQKBgQDdrAEnVlOFoCLw4sUjsPoxkLjhTAgI7CYk5NNxX67Rnj0tp+Y49+sGUhl5sCGfMKkLShiON5P2oxZa+B0aPtQjsdnsFPa1uaZkK4c++SS6AetzYRpVDLmLp7/1CulE0z3O0sBekpwiuaqLJ9ZccC81g4+2j8j6c50rIAct3hxIxw=="
|
||||
ExpressvpnTLSAuthOpenvpnStaticKeyV1 = "48d9999bd71095b10649c7cb471c1051b1afdece597cea06909b99303a18c67401597b12c04a787e98cdb619ee960d90a0165529dc650f3a5c6fbe77c91c137dcf55d863fcbe314df5f0b45dbe974d9bde33ef5b4803c3985531c6c23ca6906d6cd028efc8585d1b9e71003566bd7891b9cc9212bcba510109922eed87f5c8e66d8e59cbd82575261f02777372b2cd4ca5214c4a6513ff26dd568f574fd40d6cd450fc788160ff68434ce2bf6afb00e710a3198538f14c4d45d84ab42637872e778a6b35a124e700920879f1d003ba93dccdb953cdf32bea03f365760b0ed8002098d4ce20d045b45a83a8432cc737677aed27125592a7148d25c87fdbe0a3f6"
|
||||
ExpressvpnCA = "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"
|
||||
)
|
||||
|
||||
func ExpressvpnCountriesChoices(servers []models.ExpressvpnServer) (choices []string) {
|
||||
choices = make([]string, len(servers))
|
||||
for i := range servers {
|
||||
choices[i] = servers[i].Country
|
||||
}
|
||||
return makeUnique(choices)
|
||||
}
|
||||
|
||||
func ExpressvpnCityChoices(servers []models.ExpressvpnServer) (choices []string) {
|
||||
choices = make([]string, len(servers))
|
||||
for i := range servers {
|
||||
choices[i] = servers[i].City
|
||||
}
|
||||
return makeUnique(choices)
|
||||
}
|
||||
|
||||
func ExpressvpnHostnameChoices(servers []models.ExpressvpnServer) (choices []string) {
|
||||
choices = make([]string, len(servers))
|
||||
for i := range servers {
|
||||
choices[i] = servers[i].Hostname
|
||||
}
|
||||
return makeUnique(choices)
|
||||
}
|
||||
@@ -15,7 +15,7 @@ func FastestvpnCountriesChoices(servers []models.FastestvpnServer) (choices []st
|
||||
for i := range servers {
|
||||
choices[i] = servers[i].Country
|
||||
}
|
||||
return choices
|
||||
return makeUnique(choices)
|
||||
}
|
||||
|
||||
func FastestvpnHostnameChoices(servers []models.FastestvpnServer) (choices []string) {
|
||||
@@ -23,5 +23,5 @@ func FastestvpnHostnameChoices(servers []models.FastestvpnServer) (choices []str
|
||||
for i := range servers {
|
||||
choices[i] = servers[i].Hostname
|
||||
}
|
||||
return choices
|
||||
return makeUnique(choices)
|
||||
}
|
||||
|
||||
@@ -25,11 +25,3 @@ func NordvpnHostnameChoices(servers []models.NordvpnServer) (choices []string) {
|
||||
}
|
||||
return makeUnique(choices)
|
||||
}
|
||||
|
||||
func NordvpnNameChoices(servers []models.NordvpnServer) (choices []string) {
|
||||
choices = make([]string, len(servers))
|
||||
for i := range servers {
|
||||
choices[i] = servers[i].Name
|
||||
}
|
||||
return makeUnique(choices)
|
||||
}
|
||||
|
||||
21
internal/constants/perfectprivacy.go
Normal file
21
internal/constants/perfectprivacy.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package constants
|
||||
|
||||
import (
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
)
|
||||
|
||||
//nolint:lll
|
||||
const (
|
||||
PerfectprivacyCA = "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"
|
||||
PerfectprivacyCert = "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"
|
||||
PerfectprivacyPrivateKey = "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"
|
||||
PerfectprivacyTLSCryptOpenvpnStaticKeyV1 = "d10a8e2641f5834f6c5e04a6ee9a798553d338fa2836ef2a91057c1f6174a3a12b36f16d1110b20e42ae94d3bd579213e9c3770be6c74804348dddba876945a5a3ab7660f9436f85f331641f6efc81315f0d12b2766a9f15c10a53cf9ba32dc80f03b5f15a6cc6987bda795dbe83443ec81f3d5e161cd47fab6b1f125b3adeee1eae33370d018594e0ff6b25b815228d27371b32c82a95f4929d3abb5fa36e57bf1f42353542568fbb8233f4645f05820275f79570cb8bbcf8010fc5d20f07d031a8227d45daf7349e34158c91a3d4e5add19cfa02f683f87609f6525fa0594016d11abf2de649f83ad54edd3e74e032e34b1bca685b8499916826d9aee11c13"
|
||||
)
|
||||
|
||||
func PerfectprivacyCityChoices(servers []models.PerfectprivacyServer) (choices []string) {
|
||||
choices = make([]string, len(servers))
|
||||
for i := range servers {
|
||||
choices[i] = servers[i].City
|
||||
}
|
||||
return makeUnique(choices)
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import "github.com/qdm12/gluetun/internal/models"
|
||||
//nolint:lll
|
||||
const (
|
||||
PrivatevpnCertificate = "MIIErTCCA5WgAwIBAgIJAPp3HmtYGCIOMA0GCSqGSIb3DQEBCwUAMIGVMQswCQYDVQQGEwJTRTELMAkGA1UECBMCQ0ExEjAQBgNVBAcTCVN0b2NraG9sbTETMBEGA1UEChMKUHJpdmF0ZVZQTjEWMBQGA1UEAxMNUHJpdmF0ZVZQTiBDQTETMBEGA1UEKRMKUHJpdmF0ZVZQTjEjMCEGCSqGSIb3DQEJARYUc3VwcG9ydEBwcml2YXR2cG4uc2UwHhcNMTcwNTI0MjAxNTM3WhcNMjcwNTIyMjAxNTM3WjCBlTELMAkGA1UEBhMCU0UxCzAJBgNVBAgTAkNBMRIwEAYDVQQHEwlTdG9ja2hvbG0xEzARBgNVBAoTClByaXZhdGVWUE4xFjAUBgNVBAMTDVByaXZhdGVWUE4gQ0ExEzARBgNVBCkTClByaXZhdGVWUE4xIzAhBgkqhkiG9w0BCQEWFHN1cHBvcnRAcHJpdmF0dnBuLnNlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwjqTWbKk85WN8nd1TaBgBnBHceQWosp8mMHr4xWMTLagWRcq2Modfy7RPnBo9kyn5j/ZZwL/21gLWJbxidurGyZZdEV9Wb5KQl3DUNxa19kwAbkkEchdES61e99MjmQlWq4vGPXAHjEuDxOZ906AXglCyAvQoXcYW0mNm9yybWllVp1aBrCaZQrNYr7eoFvolqJXdQQ3FFsTBCYa5bHJcKQLBfsiqdJ/BAxhNkQtcmWNSgLy16qoxQpCsxNCxAcYnasuL4rwOP+RazBkJTPXA/2neCJC5rt+sXR9CSfiXdJGwMpYso5m31ZEd7JL2+is0FeAZ6ETrKMnEZMsTpTkdwIDAQABo4H9MIH6MB0GA1UdDgQWBBRCkBlC94zCY6VNncMnK36JxT7bazCBygYDVR0jBIHCMIG/gBRCkBlC94zCY6VNncMnK36JxT7ba6GBm6SBmDCBlTELMAkGA1UEBhMCU0UxCzAJBgNVBAgTAkNBMRIwEAYDVQQHEwlTdG9ja2hvbG0xEzARBgNVBAoTClByaXZhdGVWUE4xFjAUBgNVBAMTDVByaXZhdGVWUE4gQ0ExEzARBgNVBCkTClByaXZhdGVWUE4xIzAhBgkqhkiG9w0BCQEWFHN1cHBvcnRAcHJpdmF0dnBuLnNlggkA+ncea1gYIg4wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAayugvExKDHar7t1zyYn99Vt1NMf46J8x4Dt9TNjBml5mR9nKvWmreMUuuOhLaO8Da466KGdXeDFNLcBYZd/J2iTawE6/3fmrML9H2sa+k/+E4uU5nQ84ZGOwCinCkMalVjM8EZ0/H2RZvLAVUnvPuUz2JfJhmiRkbeE75fVuqpAm9qdE+/7lg3oICYzxa6BJPxT+Imdjy3Q/FWdsXqX6aallhohPAZlMZgZL4eXECnV8rAfzyjOJggkMDZQt3Flc0Y4iDMfzrEhSOWMkNFBFwjK0F/dnhsX+fPX6GGRpUZgZcCt/hWvypqc05/SnrdKM/vV/jV/yZe0NVzY7S8Ur5g=="
|
||||
PrivatevpnOpenvpnStaticKeyV1 = "a49082f082ca89d6a6bb4ecc7c047c6d428a1d3c8254a95206d38a61d7fbe65984214cd7d56eacc5a60803bffd677fa7294d4bfe555036339312de2dfb1335bd9d5fd94b04bba3a15fc5192aeb02fb6d8dd2ca831fad7509be5eefa8d1eaa689dc586c831a23b589c512662652ecf1bb3a4a673816aba434a04f6857b8c2f8bb265bfe48a7b8112539729d2f7d9734a720e1035188118c73fef1824d0237d5579ca382d703b4bb252acaedc753b12199f00154d3769efbcf85ef5ad6ee755cbeaa944cb98e7654286df54c793a8443f5363078e3da548ba0beed079df633283cefb256f6a4bcfc4ab2c4affc24955c1864d5458e84a7c210d0d186269e55dcf6"
|
||||
PrivatevpnOpenvpnStaticKeyV1 = "f035a3acaeffb5aedb5bc920bca26ca7ac701da88249008e03563eba6af6d2625ac8ba1e5e0921f76be004c24ae4fd43e42caf0f84269ad44d8d4c14ba45b1386f251c7330d8cc56afd16d516835645651ef7e87a723ac78ae0d49da5b2f2d78ceafcff7a6367d0712628a6547e5fc8fef93c87f7bcd6107c7b1ae68396e944aadae50111d01a5d0c67223d667bdbf1bf434bdef03644ecc5386e102724eef3872f66547eb66dc0fea8286069cb082a41c89083b28fe9f4cec25d48017f26c4fd85b25ddf2ae5448dd2bccf3eef2aacf42ef1e88c3248c689423d0b05a641e9e79dd6b9b5c40f0cc21ffdc891b9eee951477b537261cb56a958a4f490d961ecb"
|
||||
)
|
||||
|
||||
func PrivatevpnCountryChoices(servers []models.PrivatevpnServer) (choices []string) {
|
||||
|
||||
@@ -6,8 +6,13 @@ const (
|
||||
)
|
||||
|
||||
const (
|
||||
// Custom is the VPN provider name for custom
|
||||
// VPN configurations.
|
||||
Custom = "custom"
|
||||
// Cyberghost is a VPN provider.
|
||||
Cyberghost = "cyberghost"
|
||||
// Expressvpn is a VPN provider.
|
||||
Expressvpn = "expressvpn"
|
||||
// Fastestvpn is a VPN provider.
|
||||
Fastestvpn = "fastestvpn"
|
||||
// HideMyAss is a VPN provider.
|
||||
@@ -20,6 +25,8 @@ const (
|
||||
Mullvad = "mullvad"
|
||||
// Nordvpn is a VPN provider.
|
||||
Nordvpn = "nordvpn"
|
||||
// Perfectprivacy is a VPN provider.
|
||||
Perfectprivacy = "perfect privacy"
|
||||
// Privado is a VPN provider.
|
||||
Privado = "privado"
|
||||
// PrivateInternetAccess is a VPN provider.
|
||||
@@ -38,6 +45,8 @@ const (
|
||||
VPNUnlimited = "vpn unlimited"
|
||||
// Vyprvpn is a VPN provider.
|
||||
Vyprvpn = "vyprvpn"
|
||||
// WeVPN is a VPN provider.
|
||||
Wevpn = "wevpn"
|
||||
// Windscribe is a VPN provider.
|
||||
Windscribe = "windscribe"
|
||||
)
|
||||
|
||||
26
internal/constants/wevpn.go
Normal file
26
internal/constants/wevpn.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package constants
|
||||
|
||||
import "github.com/qdm12/gluetun/internal/models"
|
||||
|
||||
//nolint:lll
|
||||
const (
|
||||
WevpnCA = "MIIDQjCCAiqgAwIBAgIUPppqnRZfvGGrT4GjXFE4Q29QzgowDQYJKoZIhvcNAQELBQAwEzERMA8GA1UEAwwIQ2hhbmdlTWUwHhcNMTkxMTA1MjMzMzIzWhcNMjkxMTAyMjMzMzIzWjATMREwDwYDVQQDDAhDaGFuZ2VNZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL5DFBJlTqhXukJFWlI8TNW9+HEQCZXhyVFvQhJFF2xIGVNx51XzqxiRANjVJZJrA68kV8az0v2Dxj0SFnRWDR6pOjjdp2CyHFcgHyfv+4MrsreAtkue86bB/1ECPWaoIwtaLnwI6SEmFZl98RlI9v4M/8IE4chOnMrM/F22+2OXI//TduvTcbyOMUiiouIP8UG1FB3J5FyuaW6qPZz2G0efDoaOI+E9LSxE87OoFrII7UqdHlWxRb3nUuPU1Ee4rN/d4tFyP4AvPKfsGhVOwyGG21IdRnbXIuDi0xytkCGOZ4j2bq5zqudnp4Izt6yJgdzZpQQWK3kSHB3qTT/Yzl8CAwEAAaOBjTCBijAdBgNVHQ4EFgQUXYkoo4WbkkvbgLVdGob9RScRf3AwTgYDVR0jBEcwRYAUXYkoo4WbkkvbgLVdGob9RScRf3ChF6QVMBMxETAPBgNVBAMMCENoYW5nZU1lghQ+mmqdFl+8YatPgaNcUThDb1DOCjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAOr1XmyWBRYfTQPNvZZ+DjCfiRYzLOi2AGefZt/jETqPDF8deVbyL1fLhXZzuX+5Etlsil3PflJjpzc/FSeZRuYRaShtwF3j6I08Eww9rBkaCnsukMUcLtMOvhdAU8dUakcRA2wkQ7Z+TWdMBv5+/6MnX10An1fIz7bAy3btMEOPTEFLo8Bst1SxJtUMaqhUteSOJ1VorpK3CWfOFaXxbJAb4E0+3zt6Vsc8mY5tt6wAi8IqiN4WD79ZdvKxENK4FMkR1kNpBY97mvdf82rzpwiBuJgN5ywmH78Ghj+9T8nI6/UIqJ1y22IRYGv6dMif8fHo5WWhCv3qmCqqY8vwuxw=="
|
||||
WevpnCertificate = "MIIDTDCCAjSgAwIBAgIRAKxt8SMIXezjmHm2KDCAQdIwDQYJKoZIhvcNAQELBQAwEzERMA8GA1UEAwwIQ2hhbmdlTWUwHhcNMTkxMTA1MjMzMzI0WhcNMjkxMTAyMjMzMzI0WjAOMQwwCgYDVQQDDAN0Y3AwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvEwY2erLhMm3Mpsnybm3G6zvGyeblUAaehQVEUs+KM2/5np0Ovx0y8Iz9pIC9ITaWM0B3dM6uBsNEtylZIe4Dd9aFujunSeCFsLRf8i9AbrUombpQ6P4jzYFBxwcEw//UShwa4HZI6JuSYikdpx/dyXdBH2skahwDVc8VUFdBLLSglfKGbuzP9GsdSwQCeBRWgA3dvIzIkQkBwfnt9WQKUfRAe8e5NybaAn8Yuu9sjLkQe6eyV7toxkZTcEXdABG2vtdTEzlAsQilZzIxg3jcdeEgMgRKngng+YNP0rR5nofZ1iDlp+vBj0nuqTTJLHMrRWPIc7bdYFD/f2J49WORAgMBAAGjgZ8wgZwwCQYDVR0TBAIwADAdBgNVHQ4EFgQUmSAFmCo1FAKVq8RQF7jMxMxcMtUwTgYDVR0jBEcwRYAUXYkoo4WbkkvbgLVdGob9RScRf3ChF6QVMBMxETAPBgNVBAMMCENoYW5nZU1lghQ+mmqdFl+8YatPgaNcUThDb1DOCjATBgNVHSUEDDAKBggrBgEFBQcDAjALBgNVHQ8EBAMCB4AwDQYJKoZIhvcNAQELBQADggEBADPqdEgL+0kou8P974QEaNg1XOAXpwP0NNqbkZ/Oj9+Lp96YAhAHOAJig+RWbBktK8zu8oUUGR1qLXAWCmirlXErVuBRnadTEh3A7SOuY02BcsYAtpQ2EU9j5K/LV7nTfagkVdWy7x/av361UD4t9fv1j4YYTh4XLRp7KVXs6AGZ7T1hqPYFMUIoPpFhPzFxH4euJjfazr4SkTR6k6Vhw3pyFd6HP65vcqpzHGxFytSa8HtltBk2DpzIf8yV9TEy+gOXFaaGss0YKQ5OU1ieqZRuLVEGiu17lByYiQGyemIETJbdkyiSg93dDJRxjaTk7c8CEdpipt07ndSIPldMtXA="
|
||||
WevpnOpenvpnStaticKeyV1 = "7be66c0df0b8855e076d9e37b19f9ff3c1735ed537dee6dc786e51bdb8502f878077eeba0420a25e2b04814d22bbdcc0191a4fc396fdba1af6eb090a9d8664f18e70012ee98a2e32c28620a771d13cf3a619c417480c2c312562fffaebfd7ba73f57a28edde6c287365e6ce28291a29728da211cb53e01aa46b92f5f276c61fb46bd810b41219022c8f3d9e699fe9ade6bfcbb937fbbf6f49d741740e71c7c008a9a13c2432608038c6310b4f33588d8d234b3dffcf0823395267d73140d0e9a40e323ca92866c37073bfb072ab9de518bb9f2c65df7e219c2f114afbcf7c6e3c401cb08c3ed2901725b0601d2b5de89245719dd32506d52f149d14156215c1e"
|
||||
)
|
||||
|
||||
func WevpnCityChoices(servers []models.WevpnServer) (choices []string) {
|
||||
choices = make([]string, len(servers))
|
||||
for i := range servers {
|
||||
choices[i] = servers[i].City
|
||||
}
|
||||
return makeUnique(choices)
|
||||
}
|
||||
|
||||
func WevpnHostnameChoices(servers []models.WevpnServer) (choices []string) {
|
||||
choices = make([]string, len(servers))
|
||||
for i := range servers {
|
||||
choices[i] = servers[i].Hostname
|
||||
}
|
||||
return makeUnique(choices)
|
||||
}
|
||||
8
internal/dns/logger.go
Normal file
8
internal/dns/logger.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package dns
|
||||
|
||||
type Logger interface {
|
||||
Debug(s string)
|
||||
Info(s string)
|
||||
Warn(s string)
|
||||
Error(s string)
|
||||
}
|
||||
@@ -6,7 +6,15 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/golibs/logging"
|
||||
)
|
||||
|
||||
type logLevel uint8
|
||||
|
||||
const (
|
||||
levelDebug logLevel = iota
|
||||
levelInfo
|
||||
levelWarn
|
||||
levelError
|
||||
)
|
||||
|
||||
func (l *Loop) collectLines(ctx context.Context, done chan<- struct{},
|
||||
@@ -29,13 +37,13 @@ func (l *Loop) collectLines(ctx context.Context, done chan<- struct{},
|
||||
|
||||
line, level := processLogLine(line)
|
||||
switch level {
|
||||
case logging.LevelDebug:
|
||||
case levelDebug:
|
||||
l.logger.Debug(line)
|
||||
case logging.LevelInfo:
|
||||
case levelInfo:
|
||||
l.logger.Info(line)
|
||||
case logging.LevelWarn:
|
||||
case levelWarn:
|
||||
l.logger.Warn(line)
|
||||
case logging.LevelError:
|
||||
case levelError:
|
||||
l.logger.Error(line)
|
||||
}
|
||||
}
|
||||
@@ -43,24 +51,24 @@ func (l *Loop) collectLines(ctx context.Context, done chan<- struct{},
|
||||
|
||||
var unboundPrefix = regexp.MustCompile(`\[[0-9]{10}\] unbound\[[0-9]+:[0|1]\] `)
|
||||
|
||||
func processLogLine(s string) (filtered string, level logging.Level) {
|
||||
func processLogLine(s string) (filtered string, level logLevel) {
|
||||
prefix := unboundPrefix.FindString(s)
|
||||
filtered = s[len(prefix):]
|
||||
switch {
|
||||
case strings.HasPrefix(filtered, "notice: "):
|
||||
filtered = strings.TrimPrefix(filtered, "notice: ")
|
||||
level = logging.LevelInfo
|
||||
level = levelInfo
|
||||
case strings.HasPrefix(filtered, "info: "):
|
||||
filtered = strings.TrimPrefix(filtered, "info: ")
|
||||
level = logging.LevelInfo
|
||||
level = levelInfo
|
||||
case strings.HasPrefix(filtered, "warn: "):
|
||||
filtered = strings.TrimPrefix(filtered, "warn: ")
|
||||
level = logging.LevelWarn
|
||||
level = levelWarn
|
||||
case strings.HasPrefix(filtered, "error: "):
|
||||
filtered = strings.TrimPrefix(filtered, "error: ")
|
||||
level = logging.LevelError
|
||||
level = levelError
|
||||
default:
|
||||
level = logging.LevelInfo
|
||||
level = levelInfo
|
||||
}
|
||||
filtered = constants.ColorUnbound().Sprintf(filtered)
|
||||
return filtered, level
|
||||
|
||||
@@ -3,7 +3,6 @@ package dns
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/qdm12/golibs/logging"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -12,30 +11,30 @@ func Test_processLogLine(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
s string
|
||||
filtered string
|
||||
level logging.Level
|
||||
level logLevel
|
||||
}{
|
||||
"empty string": {"", "", logging.LevelInfo},
|
||||
"random string": {"asdasqdb", "asdasqdb", logging.LevelInfo},
|
||||
"empty string": {"", "", levelInfo},
|
||||
"random string": {"asdasqdb", "asdasqdb", levelInfo},
|
||||
"unbound notice": {
|
||||
"[1594595249] unbound[75:0] notice: init module 0: validator",
|
||||
"init module 0: validator",
|
||||
logging.LevelInfo},
|
||||
levelInfo},
|
||||
"unbound info": {
|
||||
"[1594595249] unbound[75:0] info: init module 0: validator",
|
||||
"init module 0: validator",
|
||||
logging.LevelInfo},
|
||||
levelInfo},
|
||||
"unbound warn": {
|
||||
"[1594595249] unbound[75:0] warn: init module 0: validator",
|
||||
"init module 0: validator",
|
||||
logging.LevelWarn},
|
||||
levelWarn},
|
||||
"unbound error": {
|
||||
"[1594595249] unbound[75:0] error: init module 0: validator",
|
||||
"init module 0: validator",
|
||||
logging.LevelError},
|
||||
levelError},
|
||||
"unbound unknown": {
|
||||
"[1594595249] unbound[75:0] BLA: init module 0: validator",
|
||||
"BLA: init module 0: validator",
|
||||
logging.LevelInfo},
|
||||
levelInfo},
|
||||
}
|
||||
for name, tc := range tests {
|
||||
tc := tc
|
||||
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
"github.com/qdm12/gluetun/internal/dns/state"
|
||||
"github.com/qdm12/gluetun/internal/loopstate"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/qdm12/golibs/logging"
|
||||
)
|
||||
|
||||
var _ Looper = (*Loop)(nil)
|
||||
@@ -33,7 +32,7 @@ type Loop struct {
|
||||
resolvConf string
|
||||
blockBuilder blacklist.Builder
|
||||
client *http.Client
|
||||
logger logging.Logger
|
||||
logger Logger
|
||||
userTrigger bool
|
||||
start <-chan struct{}
|
||||
running chan<- models.LoopStatus
|
||||
@@ -48,7 +47,7 @@ type Loop struct {
|
||||
const defaultBackoffTime = 10 * time.Second
|
||||
|
||||
func NewLoop(conf unbound.Configurator, settings configuration.DNS, client *http.Client,
|
||||
logger logging.Logger) *Loop {
|
||||
logger Logger) *Loop {
|
||||
start := make(chan struct{})
|
||||
running := make(chan models.LoopStatus)
|
||||
stop := make(chan struct{})
|
||||
|
||||
@@ -3,6 +3,7 @@ package dns
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/qdm12/dns/pkg/check"
|
||||
@@ -18,7 +19,8 @@ func (l *Loop) setupUnbound(ctx context.Context) (
|
||||
cancel context.CancelFunc, waitError chan error, closeStreams func(), err error) {
|
||||
err = l.updateFiles(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, nil, errUpdateFiles
|
||||
return nil, nil, nil,
|
||||
fmt.Errorf("%w: %s", errUpdateFiles, err)
|
||||
}
|
||||
|
||||
settings := l.GetSettings()
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/qdm12/gluetun/internal/routing"
|
||||
"github.com/qdm12/golibs/command"
|
||||
"github.com/qdm12/golibs/logging"
|
||||
)
|
||||
|
||||
var _ Configurator = (*Config)(nil)
|
||||
@@ -25,7 +24,7 @@ type Configurator interface {
|
||||
|
||||
type Config struct { //nolint:maligned
|
||||
runner command.Runner
|
||||
logger logging.Logger
|
||||
logger Logger
|
||||
iptablesMutex sync.Mutex
|
||||
ip6tablesMutex sync.Mutex
|
||||
defaultInterface string
|
||||
@@ -47,7 +46,7 @@ type Config struct { //nolint:maligned
|
||||
}
|
||||
|
||||
// NewConfig creates a new Config instance.
|
||||
func NewConfig(logger logging.Logger, runner command.Runner,
|
||||
func NewConfig(logger Logger, runner command.Runner,
|
||||
defaultInterface string, defaultGateway net.IP,
|
||||
localNetworks []routing.LocalNetwork, localIP net.IP) *Config {
|
||||
return &Config{
|
||||
|
||||
7
internal/firewall/logger.go
Normal file
7
internal/firewall/logger.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package firewall
|
||||
|
||||
type Logger interface {
|
||||
Debug(s string)
|
||||
Info(s string)
|
||||
Error(s string)
|
||||
}
|
||||
@@ -4,21 +4,17 @@ import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"github.com/qdm12/golibs/logging"
|
||||
)
|
||||
|
||||
type handler struct {
|
||||
logger logging.Logger
|
||||
healthErr error
|
||||
healthErrMu sync.RWMutex
|
||||
}
|
||||
|
||||
var errHealthcheckNotRunYet = errors.New("healthcheck did not run yet")
|
||||
|
||||
func newHandler(logger logging.Logger) *handler {
|
||||
func newHandler() *handler {
|
||||
return &handler{
|
||||
logger: logger,
|
||||
healthErr: errHealthcheckNotRunYet,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,6 @@ package healthcheck
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -17,13 +14,13 @@ func (s *Server) runHealthcheckLoop(ctx context.Context, done chan<- struct{}) {
|
||||
for {
|
||||
previousErr := s.handler.getErr()
|
||||
|
||||
err := healthCheck(ctx, s.resolver)
|
||||
err := healthCheck(ctx, s.pinger)
|
||||
s.handler.setErr(err)
|
||||
|
||||
if previousErr != nil && err == nil {
|
||||
s.logger.Info("healthy!")
|
||||
s.vpn.healthyTimer.Stop()
|
||||
s.vpn.healthyWait = s.config.OpenVPN.Initial
|
||||
s.vpn.healthyWait = s.config.VPN.Initial
|
||||
} else if previousErr == nil && err != nil {
|
||||
s.logger.Info("unhealthy: " + err.Error())
|
||||
s.vpn.healthyTimer.Stop()
|
||||
@@ -40,7 +37,7 @@ func (s *Server) runHealthcheckLoop(ctx context.Context, done chan<- struct{}) {
|
||||
return
|
||||
case <-timer.C:
|
||||
case <-s.vpn.healthyTimer.C:
|
||||
s.onUnhealthyOpenvpn(ctx)
|
||||
s.onUnhealthyVPN(ctx)
|
||||
}
|
||||
continue
|
||||
}
|
||||
@@ -59,20 +56,23 @@ func (s *Server) runHealthcheckLoop(ctx context.Context, done chan<- struct{}) {
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
errNoIPResolved = errors.New("no IP address resolved")
|
||||
)
|
||||
|
||||
func healthCheck(ctx context.Context, resolver *net.Resolver) (err error) {
|
||||
func healthCheck(ctx context.Context, pinger Pinger) (err error) {
|
||||
// TODO use mullvad API if current provider is Mullvad
|
||||
const domainToResolve = "github.com"
|
||||
ips, err := resolver.LookupIP(ctx, "ip", domainToResolve)
|
||||
switch {
|
||||
case err != nil:
|
||||
// If we run without root, you need to run this on the gluetun binary:
|
||||
// setcap cap_net_raw=+ep /path/to/your/compiled/binary
|
||||
// Alternatively, we could have a separate binary just for healthcheck to
|
||||
// reduce attack surface.
|
||||
errCh := make(chan error)
|
||||
go func() {
|
||||
errCh <- pinger.Run()
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
pinger.Stop()
|
||||
<-errCh
|
||||
return ctx.Err()
|
||||
case err = <-errCh:
|
||||
return err
|
||||
case len(ips) == 0:
|
||||
return fmt.Errorf("%w for %s", errNoIPResolved, domainToResolve)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
46
internal/healthcheck/health_ping_test.go
Normal file
46
internal/healthcheck/health_ping_test.go
Normal file
@@ -0,0 +1,46 @@
|
||||
//go:build integration
|
||||
|
||||
package healthcheck
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_healthCheck_ping(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const timeout = time.Second
|
||||
|
||||
testCases := map[string]struct {
|
||||
address string
|
||||
err error
|
||||
}{
|
||||
"github.com": {
|
||||
address: "github.com",
|
||||
},
|
||||
"99.99.99.99": {
|
||||
address: "99.99.99.99",
|
||||
err: context.DeadlineExceeded,
|
||||
},
|
||||
}
|
||||
|
||||
for name, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
pinger := newPinger(testCase.address)
|
||||
|
||||
err := healthCheck(ctx, pinger)
|
||||
|
||||
assert.ErrorIs(t, testCase.err, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
96
internal/healthcheck/health_test.go
Normal file
96
internal/healthcheck/health_test.go
Normal file
@@ -0,0 +1,96 @@
|
||||
package healthcheck
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_healthCheck(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
canceledCtx, cancel := context.WithCancel(context.Background())
|
||||
cancel()
|
||||
|
||||
someErr := errors.New("error")
|
||||
|
||||
testCases := map[string]struct {
|
||||
ctx context.Context
|
||||
runErr error
|
||||
stopCall bool
|
||||
err error
|
||||
}{
|
||||
"success": {
|
||||
ctx: context.Background(),
|
||||
},
|
||||
"error": {
|
||||
ctx: context.Background(),
|
||||
runErr: someErr,
|
||||
err: someErr,
|
||||
},
|
||||
"context canceled": {
|
||||
ctx: canceledCtx,
|
||||
stopCall: true,
|
||||
err: context.Canceled,
|
||||
},
|
||||
}
|
||||
|
||||
for name, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctrl := gomock.NewController(t)
|
||||
|
||||
stopped := make(chan struct{})
|
||||
|
||||
pinger := NewMockPinger(ctrl)
|
||||
pinger.EXPECT().Run().DoAndReturn(func() error {
|
||||
if testCase.stopCall {
|
||||
<-stopped
|
||||
}
|
||||
return testCase.runErr
|
||||
})
|
||||
|
||||
if testCase.stopCall {
|
||||
pinger.EXPECT().Stop().DoAndReturn(func() {
|
||||
close(stopped)
|
||||
})
|
||||
}
|
||||
|
||||
err := healthCheck(testCase.ctx, pinger)
|
||||
|
||||
assert.ErrorIs(t, testCase.err, err)
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("canceled real pinger", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
pinger := newPinger("github.com")
|
||||
|
||||
canceledCtx, cancel := context.WithCancel(context.Background())
|
||||
cancel()
|
||||
|
||||
err := healthCheck(canceledCtx, pinger)
|
||||
|
||||
assert.ErrorIs(t, context.Canceled, err)
|
||||
})
|
||||
|
||||
t.Run("ping 127.0.0.1", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
pinger := newPinger("127.0.0.1")
|
||||
|
||||
const timeout = 100 * time.Millisecond
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
err := healthCheck(ctx, pinger)
|
||||
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
}
|
||||
6
internal/healthcheck/logger.go
Normal file
6
internal/healthcheck/logger.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package healthcheck
|
||||
|
||||
type Logger interface {
|
||||
Info(s string)
|
||||
Error(s string)
|
||||
}
|
||||
@@ -14,11 +14,11 @@ type vpnHealth struct {
|
||||
healthyTimer *time.Timer
|
||||
}
|
||||
|
||||
func (s *Server) onUnhealthyOpenvpn(ctx context.Context) {
|
||||
func (s *Server) onUnhealthyVPN(ctx context.Context) {
|
||||
s.logger.Info("program has been unhealthy for " +
|
||||
s.vpn.healthyWait.String() + ": restarting OpenVPN")
|
||||
s.vpn.healthyWait.String() + ": restarting VPN")
|
||||
_, _ = s.vpn.looper.ApplyStatus(ctx, constants.Stopped)
|
||||
_, _ = s.vpn.looper.ApplyStatus(ctx, constants.Running)
|
||||
s.vpn.healthyWait += s.config.OpenVPN.Addition
|
||||
s.vpn.healthyWait += s.config.VPN.Addition
|
||||
s.vpn.healthyTimer = time.NewTimer(s.vpn.healthyWait)
|
||||
}
|
||||
|
||||
18
internal/healthcheck/ping.go
Normal file
18
internal/healthcheck/ping.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package healthcheck
|
||||
|
||||
import "github.com/go-ping/ping"
|
||||
|
||||
//go:generate mockgen -destination=pinger_mock_test.go -package healthcheck . Pinger
|
||||
|
||||
type Pinger interface {
|
||||
Run() error
|
||||
Stop()
|
||||
}
|
||||
|
||||
func newPinger(addrToPing string) (pinger *ping.Pinger) {
|
||||
const count = 1
|
||||
pinger = ping.New(addrToPing)
|
||||
pinger.Count = count
|
||||
pinger.SetPrivileged(true) // see https://github.com/go-ping/ping#linux
|
||||
return pinger
|
||||
}
|
||||
60
internal/healthcheck/pinger_mock_test.go
Normal file
60
internal/healthcheck/pinger_mock_test.go
Normal file
@@ -0,0 +1,60 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/qdm12/gluetun/internal/healthcheck (interfaces: Pinger)
|
||||
|
||||
// Package healthcheck is a generated GoMock package.
|
||||
package healthcheck
|
||||
|
||||
import (
|
||||
reflect "reflect"
|
||||
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
)
|
||||
|
||||
// MockPinger is a mock of Pinger interface.
|
||||
type MockPinger struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockPingerMockRecorder
|
||||
}
|
||||
|
||||
// MockPingerMockRecorder is the mock recorder for MockPinger.
|
||||
type MockPingerMockRecorder struct {
|
||||
mock *MockPinger
|
||||
}
|
||||
|
||||
// NewMockPinger creates a new mock instance.
|
||||
func NewMockPinger(ctrl *gomock.Controller) *MockPinger {
|
||||
mock := &MockPinger{ctrl: ctrl}
|
||||
mock.recorder = &MockPingerMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockPinger) EXPECT() *MockPingerMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// Run mocks base method.
|
||||
func (m *MockPinger) Run() error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Run")
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Run indicates an expected call of Run.
|
||||
func (mr *MockPingerMockRecorder) Run() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Run", reflect.TypeOf((*MockPinger)(nil).Run))
|
||||
}
|
||||
|
||||
// Stop mocks base method.
|
||||
func (m *MockPinger) Stop() {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "Stop")
|
||||
}
|
||||
|
||||
// Stop indicates an expected call of Stop.
|
||||
func (mr *MockPingerMockRecorder) Stop() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Stop", reflect.TypeOf((*MockPinger)(nil).Stop))
|
||||
}
|
||||
@@ -2,11 +2,9 @@ package healthcheck
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration"
|
||||
"github.com/qdm12/gluetun/internal/vpn"
|
||||
"github.com/qdm12/golibs/logging"
|
||||
)
|
||||
|
||||
var _ ServerRunner = (*Server)(nil)
|
||||
@@ -16,23 +14,23 @@ type ServerRunner interface {
|
||||
}
|
||||
|
||||
type Server struct {
|
||||
logger logging.Logger
|
||||
handler *handler
|
||||
resolver *net.Resolver
|
||||
config configuration.Health
|
||||
vpn vpnHealth
|
||||
logger Logger
|
||||
handler *handler
|
||||
pinger Pinger
|
||||
config configuration.Health
|
||||
vpn vpnHealth
|
||||
}
|
||||
|
||||
func NewServer(config configuration.Health,
|
||||
logger logging.Logger, vpnLooper vpn.Looper) *Server {
|
||||
logger Logger, vpnLooper vpn.Looper) *Server {
|
||||
return &Server{
|
||||
logger: logger,
|
||||
handler: newHandler(logger),
|
||||
resolver: net.DefaultResolver,
|
||||
config: config,
|
||||
logger: logger,
|
||||
handler: newHandler(),
|
||||
pinger: newPinger(config.AddressToPing),
|
||||
config: config,
|
||||
vpn: vpnHealth{
|
||||
looper: vpnLooper,
|
||||
healthyWait: config.OpenVPN.Initial,
|
||||
healthyWait: config.VPN.Initial,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,11 +5,9 @@ import (
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/qdm12/golibs/logging"
|
||||
)
|
||||
|
||||
func newHandler(ctx context.Context, wg *sync.WaitGroup, logger logging.Logger,
|
||||
func newHandler(ctx context.Context, wg *sync.WaitGroup, logger Logger,
|
||||
stealth, verbose bool, username, password string) http.Handler {
|
||||
const httpTimeout = 24 * time.Hour
|
||||
return &handler{
|
||||
@@ -30,7 +28,7 @@ type handler struct {
|
||||
ctx context.Context
|
||||
wg *sync.WaitGroup
|
||||
client *http.Client
|
||||
logger logging.Logger
|
||||
logger Logger
|
||||
verbose, stealth bool
|
||||
username, password string
|
||||
}
|
||||
|
||||
@@ -32,8 +32,7 @@ func (h *handler) handleHTTP(responseWriter http.ResponseWriter, request *http.R
|
||||
response, err := h.client.Do(request)
|
||||
if err != nil {
|
||||
http.Error(responseWriter, "server error", http.StatusInternalServerError)
|
||||
h.logger.Warn("cannot request " + request.URL.String() +
|
||||
" for client " + request.RemoteAddr + ": " + err.Error())
|
||||
h.logger.Warn("cannot process request for client " + request.RemoteAddr + ": " + err.Error())
|
||||
return
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
21
internal/httpproxy/logger.go
Normal file
21
internal/httpproxy/logger.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package httpproxy
|
||||
|
||||
type Logger interface {
|
||||
Debug(s string)
|
||||
infoer
|
||||
Warn(s string)
|
||||
errorer
|
||||
}
|
||||
|
||||
type infoErrorer interface {
|
||||
infoer
|
||||
errorer
|
||||
}
|
||||
|
||||
type infoer interface {
|
||||
Info(s string)
|
||||
}
|
||||
|
||||
type errorer interface {
|
||||
Error(s string)
|
||||
}
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
"github.com/qdm12/gluetun/internal/httpproxy/state"
|
||||
"github.com/qdm12/gluetun/internal/loopstate"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/qdm12/golibs/logging"
|
||||
)
|
||||
|
||||
var _ Looper = (*Loop)(nil)
|
||||
@@ -26,7 +25,7 @@ type Loop struct {
|
||||
statusManager loopstate.Manager
|
||||
state state.Manager
|
||||
// Other objects
|
||||
logger logging.Logger
|
||||
logger Logger
|
||||
// Internal channels and locks
|
||||
running chan models.LoopStatus
|
||||
stop, stopped chan struct{}
|
||||
@@ -37,7 +36,7 @@ type Loop struct {
|
||||
|
||||
const defaultBackoffTime = 10 * time.Second
|
||||
|
||||
func NewLoop(logger logging.Logger, settings configuration.HTTPProxy) *Loop {
|
||||
func NewLoop(logger Logger, settings configuration.HTTPProxy) *Loop {
|
||||
start := make(chan struct{})
|
||||
running := make(chan models.LoopStatus)
|
||||
stop := make(chan struct{})
|
||||
|
||||
@@ -5,18 +5,16 @@ import (
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/qdm12/golibs/logging"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
address string
|
||||
handler http.Handler
|
||||
logger logging.Logger
|
||||
logger infoErrorer
|
||||
internalWG *sync.WaitGroup
|
||||
}
|
||||
|
||||
func New(ctx context.Context, address string, logger logging.Logger,
|
||||
func New(ctx context.Context, address string, logger Logger,
|
||||
stealth, verbose bool, username, password string) *Server {
|
||||
wg := &sync.WaitGroup{}
|
||||
return &Server{
|
||||
|
||||
@@ -7,12 +7,14 @@ import (
|
||||
func (a AllServers) GetCopy() (servers AllServers) {
|
||||
servers = a // copy versions and timestamps
|
||||
servers.Cyberghost.Servers = a.GetCyberghost()
|
||||
servers.Expressvpn.Servers = a.GetExpressvpn()
|
||||
servers.Fastestvpn.Servers = a.GetFastestvpn()
|
||||
servers.HideMyAss.Servers = a.GetHideMyAss()
|
||||
servers.Ipvanish.Servers = a.GetIpvanish()
|
||||
servers.Ivpn.Servers = a.GetIvpn()
|
||||
servers.Mullvad.Servers = a.GetMullvad()
|
||||
servers.Nordvpn.Servers = a.GetNordvpn()
|
||||
servers.Perfectprivacy.Servers = a.GetPerfectprivacy()
|
||||
servers.Privado.Servers = a.GetPrivado()
|
||||
servers.Pia.Servers = a.GetPia()
|
||||
servers.Privatevpn.Servers = a.GetPrivatevpn()
|
||||
@@ -38,6 +40,18 @@ func (a *AllServers) GetCyberghost() (servers []CyberghostServer) {
|
||||
return servers
|
||||
}
|
||||
|
||||
func (a *AllServers) GetExpressvpn() (servers []ExpressvpnServer) {
|
||||
if a.Expressvpn.Servers == nil {
|
||||
return nil
|
||||
}
|
||||
servers = make([]ExpressvpnServer, len(a.Expressvpn.Servers))
|
||||
for i, serverToCopy := range a.Expressvpn.Servers {
|
||||
servers[i] = serverToCopy
|
||||
servers[i].IPs = copyIPs(serverToCopy.IPs)
|
||||
}
|
||||
return servers
|
||||
}
|
||||
|
||||
func (a *AllServers) GetFastestvpn() (servers []FastestvpnServer) {
|
||||
if a.Fastestvpn.Servers == nil {
|
||||
return nil
|
||||
@@ -111,6 +125,18 @@ func (a *AllServers) GetNordvpn() (servers []NordvpnServer) {
|
||||
return servers
|
||||
}
|
||||
|
||||
func (a *AllServers) GetPerfectprivacy() (servers []PerfectprivacyServer) {
|
||||
if a.Perfectprivacy.Servers == nil {
|
||||
return nil
|
||||
}
|
||||
servers = make([]PerfectprivacyServer, len(a.Perfectprivacy.Servers))
|
||||
for i, serverToCopy := range a.Perfectprivacy.Servers {
|
||||
servers[i] = serverToCopy
|
||||
servers[i].IPs = copyIPs(serverToCopy.IPs)
|
||||
}
|
||||
return servers
|
||||
}
|
||||
|
||||
func (a *AllServers) GetPia() (servers []PIAServer) {
|
||||
if a.Pia.Servers == nil {
|
||||
return nil
|
||||
@@ -155,7 +181,7 @@ func (a *AllServers) GetProtonvpn() (servers []ProtonvpnServer) {
|
||||
for i, serverToCopy := range a.Protonvpn.Servers {
|
||||
servers[i] = serverToCopy
|
||||
servers[i].EntryIP = copyIP(serverToCopy.EntryIP)
|
||||
servers[i].ExitIP = copyIP(serverToCopy.ExitIP)
|
||||
servers[i].ExitIPs = copyIPs(serverToCopy.ExitIPs)
|
||||
}
|
||||
return servers
|
||||
}
|
||||
@@ -220,6 +246,18 @@ func (a *AllServers) GetVyprvpn() (servers []VyprvpnServer) {
|
||||
return servers
|
||||
}
|
||||
|
||||
func (a *AllServers) GetWevpn() (servers []WevpnServer) {
|
||||
if a.Windscribe.Servers == nil {
|
||||
return nil
|
||||
}
|
||||
servers = make([]WevpnServer, len(a.Wevpn.Servers))
|
||||
for i, serverToCopy := range a.Wevpn.Servers {
|
||||
servers[i] = serverToCopy
|
||||
servers[i].IPs = copyIPs(serverToCopy.IPs)
|
||||
}
|
||||
return servers
|
||||
}
|
||||
|
||||
func (a *AllServers) GetWindscribe() (servers []WindscribeServer) {
|
||||
if a.Windscribe.Servers == nil {
|
||||
return nil
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user