Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5cc55c92fa |
@@ -1,2 +1,2 @@
|
|||||||
FROM qmcgaw/godevcontainer
|
FROM qmcgaw/godevcontainer
|
||||||
RUN apk add wireguard-tools htop
|
RUN apk add wireguard-tools
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
"vscode"
|
"vscode"
|
||||||
],
|
],
|
||||||
"shutdownAction": "stopCompose",
|
"shutdownAction": "stopCompose",
|
||||||
"postCreateCommand": "source ~/.windows.sh && go mod download && go mod tidy",
|
"postCreateCommand": "~/.windows.sh && go mod download && go mod tidy",
|
||||||
"workspaceFolder": "/workspace",
|
"workspaceFolder": "/workspace",
|
||||||
"extensions": [
|
"extensions": [
|
||||||
"golang.go",
|
"golang.go",
|
||||||
@@ -25,7 +25,6 @@
|
|||||||
"bajdzis.vscode-database", // Supports connections to mysql or postgres, over SSL, socked
|
"bajdzis.vscode-database", // Supports connections to mysql or postgres, over SSL, socked
|
||||||
"IBM.output-colorizer", // Colorize your output/test logs
|
"IBM.output-colorizer", // Colorize your output/test logs
|
||||||
"mohsen1.prettify-json", // Prettify JSON data
|
"mohsen1.prettify-json", // Prettify JSON data
|
||||||
"github.copilot",
|
|
||||||
],
|
],
|
||||||
"settings": {
|
"settings": {
|
||||||
"files.eol": "\n",
|
"files.eol": "\n",
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ version: "3.7"
|
|||||||
services:
|
services:
|
||||||
vscode:
|
vscode:
|
||||||
build: .
|
build: .
|
||||||
|
image: godevcontainer
|
||||||
devices:
|
devices:
|
||||||
- /dev/net/tun:/dev/net/tun
|
- /dev/net/tun:/dev/net/tun
|
||||||
volumes:
|
volumes:
|
||||||
@@ -10,16 +11,16 @@ services:
|
|||||||
# Docker socket to access Docker server
|
# Docker socket to access Docker server
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
# Docker configuration
|
# Docker configuration
|
||||||
- ~/.docker:/root/.docker
|
- ~/.docker:/root/.docker:z
|
||||||
# SSH directory for Linux, OSX and WSL
|
# SSH directory for Linux, OSX and WSL
|
||||||
- ~/.ssh:/root/.ssh
|
- ~/.ssh:/root/.ssh:z
|
||||||
# For Windows without WSL, a copy will be made
|
# For Windows without WSL, a copy will be made
|
||||||
# from /tmp/.ssh to ~/.ssh to fix permissions
|
# from /tmp/.ssh to ~/.ssh to fix permissions
|
||||||
#- ~/.ssh:/tmp/.ssh:ro
|
#- ~/.ssh:/tmp/.ssh:ro
|
||||||
# Shell history persistence
|
# Shell history persistence
|
||||||
- ~/.zsh_history:/root/.zsh_history
|
- ~/.zsh_history:/root/.zsh_history:z
|
||||||
# Git config
|
# Git config
|
||||||
- ~/.gitconfig:/root/.gitconfig
|
- ~/.gitconfig:/root/.gitconfig:z
|
||||||
environment:
|
environment:
|
||||||
- TZ=
|
- TZ=
|
||||||
cap_add:
|
cap_add:
|
||||||
|
|||||||
2
.github/ISSUE_TEMPLATE/bug.yml
vendored
2
.github/ISSUE_TEMPLATE/bug.yml
vendored
@@ -96,7 +96,7 @@ body:
|
|||||||
attributes:
|
attributes:
|
||||||
label: Share your logs
|
label: Share your logs
|
||||||
description: No sensitive information is logged out except when running with `LOG_LEVEL=debug`.
|
description: No sensitive information is logged out except when running with `LOG_LEVEL=debug`.
|
||||||
render: plain text
|
render: log
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
|
|||||||
37
.github/workflows/ci-skip.yml
vendored
37
.github/workflows/ci-skip.yml
vendored
@@ -1,37 +0,0 @@
|
|||||||
name: No trigger file paths
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
paths-ignore:
|
|
||||||
- .github/workflows/ci.yml
|
|
||||||
- cmd/**
|
|
||||||
- internal/**
|
|
||||||
- pkg/**
|
|
||||||
- .dockerignore
|
|
||||||
- .golangci.yml
|
|
||||||
- Dockerfile
|
|
||||||
- go.mod
|
|
||||||
- go.sum
|
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
paths-ignore:
|
|
||||||
- .github/workflows/ci.yml
|
|
||||||
- cmd/**
|
|
||||||
- internal/**
|
|
||||||
- pkg/**
|
|
||||||
- .dockerignore
|
|
||||||
- .golangci.yml
|
|
||||||
- Dockerfile
|
|
||||||
- go.mod
|
|
||||||
- go.sum
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
verify:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
actions: read
|
|
||||||
steps:
|
|
||||||
- name: No trigger path triggered for required verify workflow.
|
|
||||||
run: exit 0
|
|
||||||
105
.github/workflows/ci.yml
vendored
105
.github/workflows/ci.yml
vendored
@@ -32,23 +32,24 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
verify:
|
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
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
|
||||||
actions: read
|
|
||||||
contents: read
|
|
||||||
env:
|
env:
|
||||||
DOCKER_BUILDKIT: "1"
|
DOCKER_BUILDKIT: "1"
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v2.4.0
|
||||||
|
|
||||||
- uses: reviewdog/action-misspell@v1
|
|
||||||
with:
|
|
||||||
locale: "US"
|
|
||||||
level: error
|
|
||||||
|
|
||||||
- name: Linting
|
- name: Linting
|
||||||
run: docker build --target lint .
|
run: docker build --target lint .
|
||||||
|
|
||||||
|
- name: Go mod tidy check
|
||||||
|
run: docker build --target tidy .
|
||||||
|
|
||||||
- name: Build test image
|
- name: Build test image
|
||||||
run: docker build --target test -t test-container .
|
run: docker build --target test -t test-container .
|
||||||
|
|
||||||
@@ -59,71 +60,79 @@ jobs:
|
|||||||
-v "$(pwd)/coverage.txt:/tmp/gobuild/coverage.txt" \
|
-v "$(pwd)/coverage.txt:/tmp/gobuild/coverage.txt" \
|
||||||
test-container
|
test-container
|
||||||
|
|
||||||
|
- name: Code security analysis
|
||||||
|
uses: snyk/actions/golang@master
|
||||||
|
env:
|
||||||
|
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
|
||||||
|
|
||||||
- name: Build final image
|
- name: Build final image
|
||||||
run: docker build -t final-image .
|
run: docker build -t final-image .
|
||||||
|
|
||||||
codeql:
|
# - name: Image security analysis
|
||||||
runs-on: ubuntu-latest
|
# uses: snyk/actions/docker@master
|
||||||
permissions:
|
# env:
|
||||||
actions: read
|
# SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
|
||||||
contents: read
|
# with:
|
||||||
security-events: write
|
# image: final-image
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- uses: github/codeql-action/init@v2
|
|
||||||
with:
|
|
||||||
languages: go
|
|
||||||
- uses: github/codeql-action/autobuild@v2
|
|
||||||
- uses: github/codeql-action/analyze@v2
|
|
||||||
|
|
||||||
publish:
|
publish:
|
||||||
|
# Only run if it's a push event or if it's a PR from this repository
|
||||||
if: |
|
if: |
|
||||||
github.repository == 'qdm12/gluetun' &&
|
github.event_name == 'push' ||
|
||||||
(
|
github.event_name == 'release' ||
|
||||||
github.event_name == 'push' ||
|
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository)
|
||||||
github.event_name == 'release' ||
|
needs: [verify]
|
||||||
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && github.actor != 'dependabot[bot]')
|
|
||||||
)
|
|
||||||
needs: [verify, codeql]
|
|
||||||
permissions:
|
|
||||||
actions: read
|
|
||||||
contents: read
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v2.4.0
|
||||||
|
|
||||||
|
- uses: docker/setup-qemu-action@v1
|
||||||
|
- uses: docker/setup-buildx-action@v1
|
||||||
|
|
||||||
|
- uses: docker/login-action@v1
|
||||||
|
with:
|
||||||
|
username: qmcgaw
|
||||||
|
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||||
|
|
||||||
|
- name: Check for semver tag
|
||||||
|
id: semvercheck
|
||||||
|
run: |
|
||||||
|
if [[ ${{ github.ref }} =~ ^refs\/tags\/v0\.[0-9]+\.[0-9]+$ ]]; then
|
||||||
|
MATCH=true
|
||||||
|
else
|
||||||
|
MATCH=false
|
||||||
|
fi
|
||||||
|
if [[ ${{ github.ref }} =~ ^refs\/tags\/v[1-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||||
|
MATCH=$MATCH_nonzero
|
||||||
|
fi
|
||||||
|
echo ::set-output name=match::$MATCH
|
||||||
|
|
||||||
# extract metadata (tags, labels) for Docker
|
# extract metadata (tags, labels) for Docker
|
||||||
# https://github.com/docker/metadata-action
|
# https://github.com/docker/metadata-action
|
||||||
- name: Extract Docker metadata
|
- name: Extract Docker metadata
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@v4
|
uses: docker/metadata-action@v3
|
||||||
with:
|
with:
|
||||||
flavor: |
|
flavor: |
|
||||||
latest=${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }}
|
latest=${{ github.ref == 'refs/heads/master' }}
|
||||||
images: |
|
images: |
|
||||||
qmcgaw/gluetun
|
qmcgaw/gluetun
|
||||||
qmcgaw/private-internet-access
|
qmcgaw/private-internet-access
|
||||||
tags: |
|
tags: |
|
||||||
|
type=ref,event=branch,enable=${{ github.ref != 'refs/heads/master' }}
|
||||||
type=ref,event=pr
|
type=ref,event=pr
|
||||||
type=semver,pattern=v{{major}}.{{minor}}.{{patch}}
|
type=ref,event=tag,enable=${{ !startsWith(steps.semvercheck.outputs.match, 'true') }}
|
||||||
type=semver,pattern=v{{major}}.{{minor}}
|
type=semver,pattern=v{{major}}.{{minor}}.{{patch}},enable=${{ startsWith(steps.semvercheck.outputs.match, 'true') }}
|
||||||
type=semver,pattern=v{{major}},enable=${{ !startsWith(github.ref, 'refs/tags/v0.') }}
|
type=semver,pattern=v{{major}}.{{minor}},enable=${{ startsWith(steps.semvercheck.outputs.match, 'true') }}
|
||||||
type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }}
|
type=semver,pattern=v{{major}},enable=${{ startsWith(steps.semvercheck.outputs.match, 'true_nonzero') }}
|
||||||
|
type=raw,value=latest,enable=${{ !startsWith(steps.semvercheck.outputs.match, 'true') }}
|
||||||
- uses: docker/setup-qemu-action@v2
|
|
||||||
- uses: docker/setup-buildx-action@v2
|
|
||||||
|
|
||||||
- uses: docker/login-action@v2
|
|
||||||
with:
|
|
||||||
username: qmcgaw
|
|
||||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
|
||||||
|
|
||||||
- name: Short commit
|
- name: Short commit
|
||||||
id: shortcommit
|
id: shortcommit
|
||||||
run: echo "::set-output name=value::$(git rev-parse --short HEAD)"
|
run: echo "::set-output name=value::$(git rev-parse --short HEAD)"
|
||||||
|
|
||||||
- name: Build and push final image
|
- name: Build and push final image
|
||||||
uses: docker/build-push-action@v3.0.0
|
uses: docker/build-push-action@v2.7.0
|
||||||
with:
|
with:
|
||||||
platforms: linux/amd64,linux/386,linux/arm64,linux/arm/v6,linux/arm/v7,linux/ppc64le
|
platforms: linux/amd64,linux/386,linux/arm64,linux/arm/v6,linux/arm/v7,linux/ppc64le
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
|||||||
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 .
|
||||||
16
.github/workflows/dockerhub-description.yml
vendored
16
.github/workflows/dockerhub-description.yml
vendored
@@ -1,22 +1,18 @@
|
|||||||
name: Docker Hub description
|
name: Docker Hub description
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches: [master]
|
||||||
- master
|
|
||||||
paths:
|
paths:
|
||||||
- README.md
|
- README.md
|
||||||
- .github/workflows/dockerhub-description.yml
|
- .github/workflows/dockerhub-description.yml
|
||||||
jobs:
|
jobs:
|
||||||
docker-hub-description:
|
dockerHubDescription:
|
||||||
if: github.repository == 'qdm12/gluetun'
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
|
||||||
actions: read
|
|
||||||
contents: read
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2.4.0
|
||||||
- uses: peter-evans/dockerhub-description@v3
|
- name: Docker Hub Description
|
||||||
|
uses: peter-evans/dockerhub-description@v2
|
||||||
with:
|
with:
|
||||||
username: qmcgaw
|
username: qmcgaw
|
||||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||||
|
|||||||
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 .
|
||||||
4
.github/workflows/labels.yml
vendored
4
.github/workflows/labels.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
|||||||
labeler:
|
labeler:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v2.4.0
|
||||||
- uses: crazy-max/ghaction-github-labeler@v4
|
- uses: crazy-max/ghaction-github-labeler@v3
|
||||||
with:
|
with:
|
||||||
yaml-file: .github/labels.yml
|
yaml-file: .github/labels.yml
|
||||||
|
|||||||
15
.github/workflows/misspell.yml
vendored
Normal file
15
.github/workflows/misspell.yml
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
name: Misspells
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches: [master]
|
||||||
|
push:
|
||||||
|
branches: [master]
|
||||||
|
jobs:
|
||||||
|
misspell:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2.4.0
|
||||||
|
- uses: reviewdog/action-misspell@v1
|
||||||
|
with:
|
||||||
|
locale: "US"
|
||||||
|
level: error
|
||||||
@@ -7,47 +7,48 @@ issues:
|
|||||||
- path: _test\.go
|
- path: _test\.go
|
||||||
linters:
|
linters:
|
||||||
- dupl
|
- dupl
|
||||||
|
- maligned
|
||||||
- goerr113
|
- goerr113
|
||||||
- containedctx
|
- path: internal/server/
|
||||||
- path: "internal\\/server\\/.+\\.go"
|
|
||||||
linters:
|
linters:
|
||||||
- dupl
|
- dupl
|
||||||
- path: "internal\\/configuration\\/settings\\/.+\\.go"
|
- path: internal/configuration/
|
||||||
linters:
|
linters:
|
||||||
- dupl
|
- dupl
|
||||||
- text: "^mnd: Magic number: 0[0-9]{3}, in <argument> detected$"
|
- path: internal/constants/
|
||||||
source: "^.+= os\\.OpenFile\\(.+, .+, 0[0-9]{3}\\)"
|
linters:
|
||||||
|
- dupl
|
||||||
|
- text: "exported: exported var Err*"
|
||||||
|
linters:
|
||||||
|
- revive
|
||||||
|
- text: "mnd: Magic number: 0644*"
|
||||||
linters:
|
linters:
|
||||||
- gomnd
|
- gomnd
|
||||||
|
- text: "mnd: Magic number: 0400*"
|
||||||
- text: "^mnd: Magic number: 0[0-9]{3}, in <argument> detected$"
|
|
||||||
source: "^.+= os\\.MkdirAll\\(.+, 0[0-9]{3}\\)"
|
|
||||||
linters:
|
linters:
|
||||||
- gomnd
|
- gomnd
|
||||||
- linters:
|
- text: "variable 'mssFix' is only used in the if-statement*"
|
||||||
- lll
|
path: "openvpnconf.go"
|
||||||
source: "^//go:generate .+$"
|
|
||||||
- text: "returns interface \\(github\\.com\\/vishvananda\\/netlink\\.Link\\)"
|
|
||||||
linters:
|
linters:
|
||||||
- ireturn
|
- ifshort
|
||||||
|
- text: "variable 'auth' is only used in the if-statement*"
|
||||||
|
path: "openvpnconf.go"
|
||||||
|
linters:
|
||||||
|
- ifshort
|
||||||
linters:
|
linters:
|
||||||
enable:
|
enable:
|
||||||
# - cyclop
|
# - cyclop
|
||||||
# - errorlint
|
# - errorlint
|
||||||
|
# - ireturn
|
||||||
# - varnamelen
|
# - varnamelen
|
||||||
# - wrapcheck
|
# - wrapcheck
|
||||||
- asciicheck
|
- asciicheck
|
||||||
- bidichk
|
- bidichk
|
||||||
- bodyclose
|
- bodyclose
|
||||||
- containedctx
|
|
||||||
- decorder
|
|
||||||
- dogsled
|
- dogsled
|
||||||
- dupl
|
- dupl
|
||||||
- durationcheck
|
- durationcheck
|
||||||
- errchkjson
|
|
||||||
- errname
|
- errname
|
||||||
- execinquery
|
|
||||||
- exhaustive
|
- exhaustive
|
||||||
- exportloopref
|
- exportloopref
|
||||||
- forcetypeassert
|
- forcetypeassert
|
||||||
@@ -66,12 +67,9 @@ linters:
|
|||||||
- gomoddirectives
|
- gomoddirectives
|
||||||
- goprintffuncname
|
- goprintffuncname
|
||||||
- gosec
|
- gosec
|
||||||
- grouper
|
|
||||||
- ifshort
|
- ifshort
|
||||||
- importas
|
- importas
|
||||||
- ireturn
|
|
||||||
- lll
|
- lll
|
||||||
- maintidx
|
|
||||||
- makezero
|
- makezero
|
||||||
- misspell
|
- misspell
|
||||||
- nakedret
|
- nakedret
|
||||||
@@ -80,7 +78,6 @@ linters:
|
|||||||
- nilnil
|
- nilnil
|
||||||
- noctx
|
- noctx
|
||||||
- nolintlint
|
- nolintlint
|
||||||
- nosprintfhostport
|
|
||||||
- prealloc
|
- prealloc
|
||||||
- predeclared
|
- predeclared
|
||||||
- predeclared
|
- predeclared
|
||||||
|
|||||||
25
.vscode/launch.json
vendored
25
.vscode/launch.json
vendored
@@ -1,25 +0,0 @@
|
|||||||
{
|
|
||||||
"version": "0.2.0",
|
|
||||||
"configurations": [
|
|
||||||
{
|
|
||||||
"name": "Update a VPN provider servers data",
|
|
||||||
"type": "go",
|
|
||||||
"request": "launch",
|
|
||||||
"mode": "debug",
|
|
||||||
"program": "cmd/gluetun/main.go",
|
|
||||||
"args": [
|
|
||||||
"update",
|
|
||||||
"-enduser",
|
|
||||||
"-providers",
|
|
||||||
"${input:provider}"
|
|
||||||
],
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"id": "provider",
|
|
||||||
"type": "promptString",
|
|
||||||
"description": "Please enter a provider (or comma separated list of providers)",
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
68
Dockerfile
68
Dockerfile
@@ -1,8 +1,8 @@
|
|||||||
ARG ALPINE_VERSION=3.16
|
ARG ALPINE_VERSION=3.15
|
||||||
ARG GO_ALPINE_VERSION=3.16
|
ARG GO_ALPINE_VERSION=3.15
|
||||||
ARG GO_VERSION=1.17
|
ARG GO_VERSION=1.17
|
||||||
ARG XCPUTRANSLATE_VERSION=v0.6.0
|
ARG XCPUTRANSLATE_VERSION=v0.6.0
|
||||||
ARG GOLANGCI_LINT_VERSION=v1.46.2
|
ARG GOLANGCI_LINT_VERSION=v1.43.0
|
||||||
ARG BUILDPLATFORM=linux/amd64
|
ARG BUILDPLATFORM=linux/amd64
|
||||||
|
|
||||||
FROM --platform=${BUILDPLATFORM} qmcgaw/xcputranslate:${XCPUTRANSLATE_VERSION} AS xcputranslate
|
FROM --platform=${BUILDPLATFORM} qmcgaw/xcputranslate:${XCPUTRANSLATE_VERSION} AS xcputranslate
|
||||||
@@ -30,6 +30,15 @@ FROM --platform=${BUILDPLATFORM} base AS lint
|
|||||||
COPY .golangci.yml ./
|
COPY .golangci.yml ./
|
||||||
RUN golangci-lint run --timeout=10m
|
RUN golangci-lint run --timeout=10m
|
||||||
|
|
||||||
|
FROM --platform=${BUILDPLATFORM} base AS tidy
|
||||||
|
RUN git init && \
|
||||||
|
git config user.email ci@localhost && \
|
||||||
|
git config user.name ci && \
|
||||||
|
git add -A && git commit -m ci && \
|
||||||
|
sed -i '/\/\/ indirect/d' go.mod && \
|
||||||
|
go mod tidy && \
|
||||||
|
git diff --exit-code -- go.mod
|
||||||
|
|
||||||
FROM --platform=${BUILDPLATFORM} base AS build
|
FROM --platform=${BUILDPLATFORM} base AS build
|
||||||
ARG TARGETPLATFORM
|
ARG TARGETPLATFORM
|
||||||
ARG VERSION=unknown
|
ARG VERSION=unknown
|
||||||
@@ -57,12 +66,8 @@ LABEL \
|
|||||||
org.opencontainers.image.source="https://github.com/qdm12/gluetun" \
|
org.opencontainers.image.source="https://github.com/qdm12/gluetun" \
|
||||||
org.opencontainers.image.title="VPN swiss-knife like client for multiple VPN providers" \
|
org.opencontainers.image.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"
|
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 VPN_SERVICE_PROVIDER=pia \
|
ENV VPNSP=pia \
|
||||||
VPN_TYPE=openvpn \
|
VPN_TYPE=openvpn \
|
||||||
# Common VPN options
|
|
||||||
VPN_ENDPOINT_IP= \
|
|
||||||
VPN_ENDPOINT_PORT= \
|
|
||||||
VPN_INTERFACE=tun0 \
|
|
||||||
# OpenVPN
|
# OpenVPN
|
||||||
OPENVPN_PROTOCOL=udp \
|
OPENVPN_PROTOCOL=udp \
|
||||||
OPENVPN_USER= \
|
OPENVPN_USER= \
|
||||||
@@ -72,35 +77,41 @@ ENV VPN_SERVICE_PROVIDER=pia \
|
|||||||
OPENVPN_VERSION=2.5 \
|
OPENVPN_VERSION=2.5 \
|
||||||
OPENVPN_VERBOSITY=1 \
|
OPENVPN_VERBOSITY=1 \
|
||||||
OPENVPN_FLAGS= \
|
OPENVPN_FLAGS= \
|
||||||
OPENVPN_CIPHERS= \
|
OPENVPN_CIPHER= \
|
||||||
OPENVPN_AUTH= \
|
OPENVPN_AUTH= \
|
||||||
OPENVPN_PROCESS_USER= \
|
OPENVPN_ROOT=yes \
|
||||||
|
OPENVPN_TARGET_IP= \
|
||||||
OPENVPN_IPV6=off \
|
OPENVPN_IPV6=off \
|
||||||
OPENVPN_CUSTOM_CONFIG= \
|
OPENVPN_CUSTOM_CONFIG= \
|
||||||
|
OPENVPN_INTERFACE=tun0 \
|
||||||
|
OPENVPN_PORT= \
|
||||||
# Wireguard
|
# Wireguard
|
||||||
WIREGUARD_PRIVATE_KEY= \
|
WIREGUARD_PRIVATE_KEY= \
|
||||||
WIREGUARD_PRESHARED_KEY= \
|
WIREGUARD_PRESHARED_KEY= \
|
||||||
WIREGUARD_PUBLIC_KEY= \
|
WIREGUARD_PUBLIC_KEY= \
|
||||||
WIREGUARD_ADDRESSES= \
|
WIREGUARD_ADDRESS= \
|
||||||
|
WIREGUARD_ENDPOINT_IP= \
|
||||||
|
WIREGUARD_ENDPOINT_PORT= \
|
||||||
|
WIREGUARD_INTERFACE=wg0 \
|
||||||
# VPN server filtering
|
# VPN server filtering
|
||||||
SERVER_REGIONS= \
|
REGION= \
|
||||||
SERVER_COUNTRIES= \
|
COUNTRY= \
|
||||||
SERVER_CITIES= \
|
CITY= \
|
||||||
SERVER_HOSTNAMES= \
|
SERVER_HOSTNAME= \
|
||||||
# # Mullvad only:
|
# # Mullvad only:
|
||||||
ISP= \
|
ISP= \
|
||||||
OWNED_ONLY=no \
|
OWNED=no \
|
||||||
# # Private Internet Access only:
|
# # Private Internet Access only:
|
||||||
PRIVATE_INTERNET_ACCESS_OPENVPN_ENCRYPTION_PRESET= \
|
PIA_ENCRYPTION= \
|
||||||
PRIVATE_INTERNET_ACCESS_VPN_PORT_FORWARDING=off \
|
PORT_FORWARDING=off \
|
||||||
PRIVATE_INTERNET_ACCESS_VPN_PORT_FORWARDING_STATUS_FILE="/tmp/gluetun/forwarded_port" \
|
PORT_FORWARDING_STATUS_FILE="/tmp/gluetun/forwarded_port" \
|
||||||
# # Cyberghost only:
|
# # Cyberghost only:
|
||||||
OPENVPN_CLIENTCRT_SECRETFILE=/run/secrets/openvpn_clientcrt \
|
OPENVPN_CLIENTCRT_SECRETFILE=/run/secrets/openvpn_clientcrt \
|
||||||
OPENVPN_CLIENTKEY_SECRETFILE=/run/secrets/openvpn_clientkey \
|
OPENVPN_CLIENTKEY_SECRETFILE=/run/secrets/openvpn_clientkey \
|
||||||
# # Nordvpn only:
|
# # Nordvpn only:
|
||||||
SERVER_NUMBER= \
|
SERVER_NUMBER= \
|
||||||
# # PIA only:
|
# # PIA and ProtonVPN only:
|
||||||
SERVER_NAMES= \
|
SERVER_NAME= \
|
||||||
# # ProtonVPN only:
|
# # ProtonVPN only:
|
||||||
FREE_ONLY= \
|
FREE_ONLY= \
|
||||||
# # Surfshark only:
|
# # Surfshark only:
|
||||||
@@ -115,7 +126,7 @@ ENV VPN_SERVICE_PROVIDER=pia \
|
|||||||
LOG_LEVEL=info \
|
LOG_LEVEL=info \
|
||||||
# Health
|
# Health
|
||||||
HEALTH_SERVER_ADDRESS=127.0.0.1:9999 \
|
HEALTH_SERVER_ADDRESS=127.0.0.1:9999 \
|
||||||
HEALTH_TARGET_ADDRESS=cloudflare.com:443 \
|
HEALTH_TARGET_ADDRESS=github.com:443 \
|
||||||
HEALTH_VPN_DURATION_INITIAL=6s \
|
HEALTH_VPN_DURATION_INITIAL=6s \
|
||||||
HEALTH_VPN_DURATION_ADDITION=5s \
|
HEALTH_VPN_DURATION_ADDITION=5s \
|
||||||
# DNS over TLS
|
# DNS over TLS
|
||||||
@@ -132,7 +143,7 @@ ENV VPN_SERVICE_PROVIDER=pia \
|
|||||||
BLOCK_ADS=off \
|
BLOCK_ADS=off \
|
||||||
UNBLOCK= \
|
UNBLOCK= \
|
||||||
DNS_UPDATE_PERIOD=24h \
|
DNS_UPDATE_PERIOD=24h \
|
||||||
DNS_ADDRESS=127.0.0.1 \
|
DNS_PLAINTEXT_ADDRESS=127.0.0.1 \
|
||||||
DNS_KEEP_NAMESERVER=off \
|
DNS_KEEP_NAMESERVER=off \
|
||||||
# HTTP proxy
|
# HTTP proxy
|
||||||
HTTPPROXY= \
|
HTTPPROXY= \
|
||||||
@@ -149,20 +160,11 @@ ENV VPN_SERVICE_PROVIDER=pia \
|
|||||||
SHADOWSOCKS_PASSWORD= \
|
SHADOWSOCKS_PASSWORD= \
|
||||||
SHADOWSOCKS_PASSWORD_SECRETFILE=/run/secrets/shadowsocks_password \
|
SHADOWSOCKS_PASSWORD_SECRETFILE=/run/secrets/shadowsocks_password \
|
||||||
SHADOWSOCKS_CIPHER=chacha20-ietf-poly1305 \
|
SHADOWSOCKS_CIPHER=chacha20-ietf-poly1305 \
|
||||||
# Control server
|
|
||||||
HTTP_CONTROL_SERVER_ADDRESS=":8000" \
|
|
||||||
# Server data updater
|
# Server data updater
|
||||||
UPDATER_PERIOD=0 \
|
UPDATER_PERIOD=0 \
|
||||||
UPDATER_MIN_RATIO=0.8 \
|
|
||||||
UPDATER_VPN_SERVICE_PROVIDERS= \
|
|
||||||
# Public IP
|
# Public IP
|
||||||
PUBLICIP_FILE="/tmp/gluetun/ip" \
|
PUBLICIP_FILE="/tmp/gluetun/ip" \
|
||||||
PUBLICIP_PERIOD=12h \
|
PUBLICIP_PERIOD=12h \
|
||||||
# Pprof
|
|
||||||
PPROF_ENABLED=no \
|
|
||||||
PPROF_BLOCK_PROFILE_RATE=0 \
|
|
||||||
PPROF_MUTEX_PROFILE_RATE=0 \
|
|
||||||
PPROF_HTTP_SERVER_ADDRESS=":6060" \
|
|
||||||
# Extras
|
# Extras
|
||||||
VERSION_INFORMATION=on \
|
VERSION_INFORMATION=on \
|
||||||
TZ= \
|
TZ= \
|
||||||
@@ -173,7 +175,7 @@ EXPOSE 8000/tcp 8888/tcp 8388/tcp 8388/udp
|
|||||||
HEALTHCHECK --interval=5s --timeout=5s --start-period=10s --retries=1 CMD /gluetun-entrypoint healthcheck
|
HEALTHCHECK --interval=5s --timeout=5s --start-period=10s --retries=1 CMD /gluetun-entrypoint healthcheck
|
||||||
ARG TARGETPLATFORM
|
ARG TARGETPLATFORM
|
||||||
RUN apk add --no-cache --update -l apk-tools && \
|
RUN apk add --no-cache --update -l apk-tools && \
|
||||||
apk add --no-cache --update -X "https://dl-cdn.alpinelinux.org/alpine/v3.12/main" openvpn==2.4.12-r0 && \
|
apk add --no-cache --update -X "https://dl-cdn.alpinelinux.org/alpine/v3.12/main" openvpn==2.4.11-r0 && \
|
||||||
mv /usr/sbin/openvpn /usr/sbin/openvpn2.4 && \
|
mv /usr/sbin/openvpn /usr/sbin/openvpn2.4 && \
|
||||||
apk del openvpn && \
|
apk del openvpn && \
|
||||||
apk add --no-cache --update openvpn ca-certificates iptables ip6tables unbound tzdata && \
|
apk add --no-cache --update openvpn ca-certificates iptables ip6tables unbound tzdata && \
|
||||||
|
|||||||
16
README.md
16
README.md
@@ -1,6 +1,11 @@
|
|||||||
# Gluetun VPN client
|
# Gluetun VPN client
|
||||||
|
|
||||||
Lightweight swiss-knife-like VPN client to multiple VPN sercice providers
|
*Lightweight swiss-knife-like VPN client to tunnel to Cyberghost, ExpressVPN, FastestVPN,
|
||||||
|
HideMyAss, IPVanish, IVPN, Mullvad, NordVPN, Perfect Privacy, Privado, Private Internet Access, PrivateVPN,
|
||||||
|
ProtonVPN, PureVPN, Surfshark, TorGuard, VPNUnlimited, VyprVPN, WeVPN and Windscribe VPN servers
|
||||||
|
using Go, OpenVPN or Wireguard, iptables, DNS over TLS, ShadowSocks and an HTTP proxy*
|
||||||
|
|
||||||
|
**ANNOUNCEMENT**: Large settings refactor merged on 2022-06-01, please file issues if you find any problem!
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -48,7 +53,6 @@ Lightweight swiss-knife-like VPN client to multiple VPN sercice providers
|
|||||||
- Sponsor me on [github.com/sponsors/qdm12](https://github.com/sponsors/qdm12)
|
- Sponsor me on [github.com/sponsors/qdm12](https://github.com/sponsors/qdm12)
|
||||||
- Donate to [paypal.me/qmcgaw](https://www.paypal.me/qmcgaw)
|
- Donate to [paypal.me/qmcgaw](https://www.paypal.me/qmcgaw)
|
||||||
- Drop me [an email](mailto:quentin.mcgaw@gmail.com)
|
- Drop me [an email](mailto:quentin.mcgaw@gmail.com)
|
||||||
- **Want to add a VPN provider?** check [Development](https://github.com/qdm12/gluetun/wiki/Development) and [Add a provider](https://github.com/qdm12/gluetun/wiki/Add-a-provider)
|
|
||||||
- Video:
|
- Video:
|
||||||
|
|
||||||
[](https://youtu.be/0F6I03LQcI4)
|
[](https://youtu.be/0F6I03LQcI4)
|
||||||
@@ -57,7 +61,7 @@ Lightweight swiss-knife-like VPN client to multiple VPN sercice providers
|
|||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Based on Alpine 3.16 for a small Docker image of 29MB
|
- Based on Alpine 3.15 for a small Docker image of 29MB
|
||||||
- Supports: **Cyberghost**, **ExpressVPN**, **FastestVPN**, **HideMyAss**, **IPVanish**, **IVPN**, **Mullvad**, **NordVPN**, **Perfect Privacy**, **Privado**, **Private Internet Access**, **PrivateVPN**, **ProtonVPN**, **PureVPN**, **Surfshark**, **TorGuard**, **VPNUnlimited**, **Vyprvpn**, **WeVPN**, **Windscribe** servers
|
- 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 OpenVPN for all providers listed
|
||||||
- Supports Wireguard both kernelspace and userspace
|
- Supports Wireguard both kernelspace and userspace
|
||||||
@@ -98,8 +102,6 @@ services:
|
|||||||
# line above must be uncommented to allow external containers to connect. See https://github.com/qdm12/gluetun/wiki/Connect-a-container-to-gluetun#external-container-to-gluetun
|
# line above must be uncommented to allow external containers to connect. See https://github.com/qdm12/gluetun/wiki/Connect-a-container-to-gluetun#external-container-to-gluetun
|
||||||
cap_add:
|
cap_add:
|
||||||
- NET_ADMIN
|
- NET_ADMIN
|
||||||
devices:
|
|
||||||
- /dev/net/tun:/dev/net/tun
|
|
||||||
ports:
|
ports:
|
||||||
- 8888:8888/tcp # HTTP proxy
|
- 8888:8888/tcp # HTTP proxy
|
||||||
- 8388:8388/tcp # Shadowsocks
|
- 8388:8388/tcp # Shadowsocks
|
||||||
@@ -108,14 +110,14 @@ services:
|
|||||||
- /yourpath:/gluetun
|
- /yourpath:/gluetun
|
||||||
environment:
|
environment:
|
||||||
# See https://github.com/qdm12/gluetun/wiki
|
# See https://github.com/qdm12/gluetun/wiki
|
||||||
- VPN_SERVICE_PROVIDER=ivpn
|
- VPNSP=ivpn
|
||||||
- VPN_TYPE=openvpn
|
- VPN_TYPE=openvpn
|
||||||
# OpenVPN:
|
# OpenVPN:
|
||||||
- OPENVPN_USER=
|
- OPENVPN_USER=
|
||||||
- OPENVPN_PASSWORD=
|
- OPENVPN_PASSWORD=
|
||||||
# Wireguard:
|
# Wireguard:
|
||||||
# - WIREGUARD_PRIVATE_KEY=wOEI9rqqbDwnN8/Bpp22sVz48T71vJ4fYmFWujulwUU=
|
# - WIREGUARD_PRIVATE_KEY=wOEI9rqqbDwnN8/Bpp22sVz48T71vJ4fYmFWujulwUU=
|
||||||
# - WIREGUARD_ADDRESSES=10.64.222.21/32
|
# - WIREGUARD_ADDRESS=10.64.222.21/32
|
||||||
# Timezone for accurate log times
|
# Timezone for accurate log times
|
||||||
- TZ=
|
- TZ=
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -29,28 +29,22 @@ import (
|
|||||||
"github.com/qdm12/gluetun/internal/models"
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
"github.com/qdm12/gluetun/internal/netlink"
|
"github.com/qdm12/gluetun/internal/netlink"
|
||||||
"github.com/qdm12/gluetun/internal/openvpn"
|
"github.com/qdm12/gluetun/internal/openvpn"
|
||||||
"github.com/qdm12/gluetun/internal/openvpn/extract"
|
|
||||||
"github.com/qdm12/gluetun/internal/portforward"
|
"github.com/qdm12/gluetun/internal/portforward"
|
||||||
"github.com/qdm12/gluetun/internal/pprof"
|
|
||||||
"github.com/qdm12/gluetun/internal/provider"
|
|
||||||
"github.com/qdm12/gluetun/internal/publicip"
|
"github.com/qdm12/gluetun/internal/publicip"
|
||||||
"github.com/qdm12/gluetun/internal/publicip/ipinfo"
|
|
||||||
"github.com/qdm12/gluetun/internal/routing"
|
"github.com/qdm12/gluetun/internal/routing"
|
||||||
"github.com/qdm12/gluetun/internal/server"
|
"github.com/qdm12/gluetun/internal/server"
|
||||||
"github.com/qdm12/gluetun/internal/shadowsocks"
|
"github.com/qdm12/gluetun/internal/shadowsocks"
|
||||||
"github.com/qdm12/gluetun/internal/storage"
|
"github.com/qdm12/gluetun/internal/storage"
|
||||||
"github.com/qdm12/gluetun/internal/tun"
|
"github.com/qdm12/gluetun/internal/tun"
|
||||||
updater "github.com/qdm12/gluetun/internal/updater/loop"
|
"github.com/qdm12/gluetun/internal/updater"
|
||||||
"github.com/qdm12/gluetun/internal/updater/resolver"
|
|
||||||
"github.com/qdm12/gluetun/internal/updater/unzip"
|
|
||||||
"github.com/qdm12/gluetun/internal/vpn"
|
"github.com/qdm12/gluetun/internal/vpn"
|
||||||
"github.com/qdm12/golibs/command"
|
"github.com/qdm12/golibs/command"
|
||||||
|
"github.com/qdm12/golibs/logging"
|
||||||
"github.com/qdm12/goshutdown"
|
"github.com/qdm12/goshutdown"
|
||||||
"github.com/qdm12/goshutdown/goroutine"
|
"github.com/qdm12/goshutdown/goroutine"
|
||||||
"github.com/qdm12/goshutdown/group"
|
"github.com/qdm12/goshutdown/group"
|
||||||
"github.com/qdm12/goshutdown/order"
|
"github.com/qdm12/goshutdown/order"
|
||||||
"github.com/qdm12/gosplash"
|
"github.com/qdm12/gosplash"
|
||||||
"github.com/qdm12/log"
|
|
||||||
"github.com/qdm12/updated/pkg/dnscrypto"
|
"github.com/qdm12/updated/pkg/dnscrypto"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -61,6 +55,11 @@ var (
|
|||||||
created = "an unknown date"
|
created = "an unknown date"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errSetupRouting = errors.New("cannot setup routing")
|
||||||
|
errCreateUser = errors.New("cannot create user")
|
||||||
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
buildInfo := models.BuildInformation{
|
buildInfo := models.BuildInformation{
|
||||||
Version: version,
|
Version: version,
|
||||||
@@ -69,11 +68,12 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
background := context.Background()
|
background := context.Background()
|
||||||
signalCh := make(chan os.Signal, 1)
|
signalCtx, stop := signal.NotifyContext(background, syscall.SIGINT, syscall.SIGTERM, os.Interrupt)
|
||||||
signal.Notify(signalCh, os.Interrupt, syscall.SIGTERM)
|
|
||||||
ctx, cancel := context.WithCancel(background)
|
ctx, cancel := context.WithCancel(background)
|
||||||
|
|
||||||
logger := log.New(log.SetLevel(log.LevelInfo))
|
logger := logging.New(logging.Settings{
|
||||||
|
Level: logging.LevelInfo,
|
||||||
|
})
|
||||||
|
|
||||||
args := os.Args
|
args := os.Args
|
||||||
tun := tun.New()
|
tun := tun.New()
|
||||||
@@ -92,11 +92,13 @@ func main() {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case signal := <-signalCh:
|
case <-signalCtx.Done():
|
||||||
|
stop()
|
||||||
fmt.Println("")
|
fmt.Println("")
|
||||||
logger.Warn("Caught OS signal " + signal.String() + ", shutting down")
|
logger.Warn("Caught OS signal, shutting down")
|
||||||
cancel()
|
cancel()
|
||||||
case err := <-errorCh:
|
case err := <-errorCh:
|
||||||
|
stop()
|
||||||
close(errorCh)
|
close(errorCh)
|
||||||
if err == nil { // expected exit such as healthcheck
|
if err == nil { // expected exit such as healthcheck
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
@@ -115,8 +117,6 @@ func main() {
|
|||||||
logger.Info("Shutdown successful")
|
logger.Info("Shutdown successful")
|
||||||
case <-timer.C:
|
case <-timer.C:
|
||||||
logger.Warn("Shutdown timed out")
|
logger.Warn("Shutdown timed out")
|
||||||
case signal := <-signalCh:
|
|
||||||
logger.Warn("Caught OS signal " + signal.String() + ", forcing shut down")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
@@ -126,11 +126,11 @@ var (
|
|||||||
errCommandUnknown = errors.New("command is unknown")
|
errCommandUnknown = errors.New("command is unknown")
|
||||||
)
|
)
|
||||||
|
|
||||||
//nolint:gocognit,gocyclo,maintidx
|
//nolint:gocognit,gocyclo
|
||||||
func _main(ctx context.Context, buildInfo models.BuildInformation,
|
func _main(ctx context.Context, buildInfo models.BuildInformation,
|
||||||
args []string, logger log.LoggerInterface, source sources.Source,
|
args []string, logger logging.ParentLogger, source sources.Source,
|
||||||
tun Tun, netLinker netLinker, cmder command.RunStarter,
|
tun tun.Interface, netLinker netlink.NetLinker, cmder command.RunStarter,
|
||||||
cli clier) error {
|
cli cli.CLIer) error {
|
||||||
if len(args) > 1 { // cli operation
|
if len(args) > 1 { // cli operation
|
||||||
switch args[1] {
|
switch args[1] {
|
||||||
case "healthcheck":
|
case "healthcheck":
|
||||||
@@ -174,62 +174,21 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: no need to validate minimal settings for the firewall:
|
|
||||||
// - global log level is parsed from source
|
|
||||||
// - firewall Debug and Enabled are booleans parsed from source
|
|
||||||
|
|
||||||
logger.Patch(log.SetLevel(*allSettings.Log.Level))
|
|
||||||
|
|
||||||
routingLogger := logger.New(log.SetComponent("routing"))
|
|
||||||
if *allSettings.Firewall.Debug { // To remove in v4
|
|
||||||
routingLogger.Patch(log.SetLevel(log.LevelDebug))
|
|
||||||
}
|
|
||||||
routingConf := routing.New(netLinker, routingLogger)
|
|
||||||
|
|
||||||
defaultRoutes, err := routingConf.DefaultRoutes()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
localNetworks, err := routingConf.LocalNetworks()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
firewallLogger := logger.New(log.SetComponent("firewall"))
|
|
||||||
if *allSettings.Firewall.Debug { // To remove in v4
|
|
||||||
firewallLogger.Patch(log.SetLevel(log.LevelDebug))
|
|
||||||
}
|
|
||||||
firewallConf, err := firewall.NewConfig(ctx, firewallLogger, cmder,
|
|
||||||
defaultRoutes, localNetworks)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if *allSettings.Firewall.Enabled {
|
|
||||||
err = firewallConf.SetEnabled(ctx, true)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO run this in a loop or in openvpn to reload from file without restarting
|
// TODO run this in a loop or in openvpn to reload from file without restarting
|
||||||
storageLogger := logger.New(log.SetComponent("storage"))
|
storageLogger := logger.NewChild(logging.Settings{Prefix: "storage: "})
|
||||||
storage, err := storage.New(storageLogger, constants.ServersData)
|
storage, err := storage.New(storageLogger, constants.ServersData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = allSettings.Validate(storage)
|
allServers := storage.GetServers()
|
||||||
|
|
||||||
|
err = allSettings.Validate(allServers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
allSettings.Pprof.HTTPServer.Logger = logger.New(log.SetComponent("pprof"))
|
logger.PatchLevel(*allSettings.Log.Level)
|
||||||
pprofServer, err := pprof.New(allSettings.Pprof)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("cannot create Pprof server: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
puid, pgid := int(*allSettings.System.PUID), int(*allSettings.System.PGID)
|
puid, pgid := int(*allSettings.System.PUID), int(*allSettings.System.PGID)
|
||||||
|
|
||||||
@@ -238,7 +197,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
|||||||
// Create configurators
|
// Create configurators
|
||||||
alpineConf := alpine.New()
|
alpineConf := alpine.New()
|
||||||
ovpnConf := openvpn.New(
|
ovpnConf := openvpn.New(
|
||||||
logger.New(log.SetComponent("openvpn configurator")),
|
logger.NewChild(logging.Settings{Prefix: "openvpn configurator: "}),
|
||||||
cmder, puid, pgid)
|
cmder, puid, pgid)
|
||||||
dnsCrypto := dnscrypto.New(httpClient, "", "")
|
dnsCrypto := dnscrypto.New(httpClient, "", "")
|
||||||
const cacertsPath = "/etc/ssl/certs/ca-certificates.crt"
|
const cacertsPath = "/etc/ssl/certs/ca-certificates.crt"
|
||||||
@@ -270,7 +229,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
|||||||
const defaultUsername = "nonrootuser"
|
const defaultUsername = "nonrootuser"
|
||||||
nonRootUsername, err := alpineConf.CreateUser(defaultUsername, puid)
|
nonRootUsername, err := alpineConf.CreateUser(defaultUsername, puid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot create user: %w", err)
|
return fmt.Errorf("%w: %s", errCreateUser, err)
|
||||||
}
|
}
|
||||||
if nonRootUsername != defaultUsername {
|
if nonRootUsername != defaultUsername {
|
||||||
logger.Info("using existing username " + nonRootUsername + " corresponding to user id " + fmt.Sprint(puid))
|
logger.Info("using existing username " + nonRootUsername + " corresponding to user id " + fmt.Sprint(puid))
|
||||||
@@ -278,22 +237,54 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
|||||||
// set it for Unbound
|
// set it for Unbound
|
||||||
// TODO remove this when migrating to qdm12/dns v2
|
// TODO remove this when migrating to qdm12/dns v2
|
||||||
allSettings.DNS.DoT.Unbound.Username = nonRootUsername
|
allSettings.DNS.DoT.Unbound.Username = nonRootUsername
|
||||||
allSettings.VPN.OpenVPN.ProcessUser = nonRootUsername
|
allSettings.VPN.OpenVPN.ProcUser = nonRootUsername
|
||||||
|
|
||||||
if err := os.Chown("/etc/unbound", puid, pgid); err != nil {
|
if err := os.Chown("/etc/unbound", puid, pgid); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
firewallLogLevel := *allSettings.Log.Level
|
||||||
|
if *allSettings.Firewall.Debug {
|
||||||
|
firewallLogLevel = logging.LevelDebug
|
||||||
|
}
|
||||||
|
routingLogger := logger.NewChild(logging.Settings{
|
||||||
|
Prefix: "routing: ",
|
||||||
|
Level: firewallLogLevel,
|
||||||
|
})
|
||||||
|
routingConf := routing.New(netLinker, routingLogger)
|
||||||
|
|
||||||
|
defaultInterface, defaultGateway, err := routingConf.DefaultRoute()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
localNetworks, err := routingConf.LocalNetworks()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultIP, err := routingConf.DefaultIP()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
firewallLogger := logger.NewChild(logging.Settings{
|
||||||
|
Prefix: "firewall: ",
|
||||||
|
Level: firewallLogLevel,
|
||||||
|
})
|
||||||
|
firewallConf := firewall.NewConfig(firewallLogger, cmder,
|
||||||
|
defaultInterface, defaultGateway, localNetworks, defaultIP)
|
||||||
|
|
||||||
if err := routingConf.Setup(); err != nil {
|
if err := routingConf.Setup(); err != nil {
|
||||||
if strings.Contains(err.Error(), "operation not permitted") {
|
if strings.Contains(err.Error(), "operation not permitted") {
|
||||||
logger.Warn("💡 Tip: Are you passing NET_ADMIN capability to gluetun?")
|
logger.Warn("💡 Tip: Are you passing NET_ADMIN capability to gluetun?")
|
||||||
}
|
}
|
||||||
return fmt.Errorf("cannot setup routing: %w", err)
|
return fmt.Errorf("%w: %s", errSetupRouting, err)
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
routingLogger.Info("routing cleanup...")
|
logger.Info("routing cleanup...")
|
||||||
if err := routingConf.TearDown(); err != nil {
|
if err := routingConf.TearDown(); err != nil {
|
||||||
routingLogger.Error("cannot teardown routing: " + err.Error())
|
logger.Error("cannot teardown routing: " + err.Error())
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -304,21 +295,25 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
const tunDevice = "/dev/net/tun"
|
if err := tun.Check(constants.TunnelDevice); err != nil {
|
||||||
if err := tun.Check(tunDevice); err != nil {
|
|
||||||
logger.Info(err.Error() + "; creating it...")
|
logger.Info(err.Error() + "; creating it...")
|
||||||
err = tun.Create(tunDevice)
|
err = tun.Create(constants.TunnelDevice)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if *allSettings.Firewall.Enabled {
|
||||||
|
err := firewallConf.SetEnabled(ctx, true) // disabled by default
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, port := range allSettings.Firewall.InputPorts {
|
for _, port := range allSettings.Firewall.InputPorts {
|
||||||
for _, defaultRoute := range defaultRoutes {
|
err = firewallConf.SetAllowedPort(ctx, port, defaultInterface)
|
||||||
err = firewallConf.SetAllowedPort(ctx, port, defaultRoute.NetInterface)
|
if err != nil {
|
||||||
if err != nil {
|
return err
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} // TODO move inside firewall?
|
} // TODO move inside firewall?
|
||||||
|
|
||||||
@@ -339,20 +334,14 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
|||||||
tickersGroupHandler := goshutdown.NewGroupHandler("tickers", defaultGroupOptions...)
|
tickersGroupHandler := goshutdown.NewGroupHandler("tickers", defaultGroupOptions...)
|
||||||
otherGroupHandler := goshutdown.NewGroupHandler("other", defaultGroupOptions...)
|
otherGroupHandler := goshutdown.NewGroupHandler("other", defaultGroupOptions...)
|
||||||
|
|
||||||
pprofReady := make(chan struct{})
|
portForwardLogger := logger.NewChild(logging.Settings{Prefix: "port forwarding: "})
|
||||||
pprofHandler, pprofCtx, pprofDone := goshutdown.NewGoRoutineHandler("pprof server")
|
|
||||||
go pprofServer.Run(pprofCtx, pprofReady, pprofDone)
|
|
||||||
otherGroupHandler.Add(pprofHandler)
|
|
||||||
<-pprofReady
|
|
||||||
|
|
||||||
portForwardLogger := logger.New(log.SetComponent("port forwarding"))
|
|
||||||
portForwardLooper := portforward.NewLoop(allSettings.VPN.Provider.PortForwarding,
|
portForwardLooper := portforward.NewLoop(allSettings.VPN.Provider.PortForwarding,
|
||||||
httpClient, firewallConf, portForwardLogger, puid, pgid)
|
httpClient, firewallConf, portForwardLogger)
|
||||||
portForwardHandler, portForwardCtx, portForwardDone := goshutdown.NewGoRoutineHandler(
|
portForwardHandler, portForwardCtx, portForwardDone := goshutdown.NewGoRoutineHandler(
|
||||||
"port forwarding", goroutine.OptionTimeout(time.Second))
|
"port forwarding", goroutine.OptionTimeout(time.Second))
|
||||||
go portForwardLooper.Run(portForwardCtx, portForwardDone)
|
go portForwardLooper.Run(portForwardCtx, portForwardDone)
|
||||||
|
|
||||||
unboundLogger := logger.New(log.SetComponent("dns over tls"))
|
unboundLogger := logger.NewChild(logging.Settings{Prefix: "dns over tls: "})
|
||||||
unboundLooper := dns.NewLoop(dnsConf, allSettings.DNS, httpClient,
|
unboundLooper := dns.NewLoop(dnsConf, allSettings.DNS, httpClient,
|
||||||
unboundLogger)
|
unboundLogger)
|
||||||
dnsHandler, dnsCtx, dnsDone := goshutdown.NewGoRoutineHandler(
|
dnsHandler, dnsCtx, dnsDone := goshutdown.NewGoRoutineHandler(
|
||||||
@@ -366,9 +355,8 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
|||||||
go unboundLooper.RunRestartTicker(dnsTickerCtx, dnsTickerDone)
|
go unboundLooper.RunRestartTicker(dnsTickerCtx, dnsTickerDone)
|
||||||
controlGroupHandler.Add(dnsTickerHandler)
|
controlGroupHandler.Add(dnsTickerHandler)
|
||||||
|
|
||||||
ipFetcher := ipinfo.New(httpClient)
|
publicIPLooper := publicip.NewLoop(httpClient,
|
||||||
publicIPLooper := publicip.NewLoop(ipFetcher,
|
logger.NewChild(logging.Settings{Prefix: "ip getter: "}),
|
||||||
logger.New(log.SetComponent("ip getter")),
|
|
||||||
allSettings.PublicIP, puid, pgid)
|
allSettings.PublicIP, puid, pgid)
|
||||||
pubIPHandler, pubIPCtx, pubIPDone := goshutdown.NewGoRoutineHandler(
|
pubIPHandler, pubIPCtx, pubIPDone := goshutdown.NewGoRoutineHandler(
|
||||||
"public IP", goroutine.OptionTimeout(defaultShutdownTimeout))
|
"public IP", goroutine.OptionTimeout(defaultShutdownTimeout))
|
||||||
@@ -380,25 +368,18 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
|||||||
go publicIPLooper.RunRestartTicker(pubIPTickerCtx, pubIPTickerDone)
|
go publicIPLooper.RunRestartTicker(pubIPTickerCtx, pubIPTickerDone)
|
||||||
tickersGroupHandler.Add(pubIPTickerHandler)
|
tickersGroupHandler.Add(pubIPTickerHandler)
|
||||||
|
|
||||||
updaterLogger := logger.New(log.SetComponent("updater"))
|
vpnLogger := logger.NewChild(logging.Settings{Prefix: "vpn: "})
|
||||||
|
|
||||||
unzipper := unzip.New(httpClient)
|
|
||||||
parallelResolver := resolver.NewParallelResolver(allSettings.Updater.DNSAddress)
|
|
||||||
openvpnFileExtractor := extract.New()
|
|
||||||
providers := provider.NewProviders(storage, time.Now, updaterLogger,
|
|
||||||
httpClient, unzipper, parallelResolver, ipFetcher, openvpnFileExtractor)
|
|
||||||
|
|
||||||
vpnLogger := logger.New(log.SetComponent("vpn"))
|
|
||||||
vpnLooper := vpn.NewLoop(allSettings.VPN, allSettings.Firewall.VPNInputPorts,
|
vpnLooper := vpn.NewLoop(allSettings.VPN, allSettings.Firewall.VPNInputPorts,
|
||||||
providers, storage, ovpnConf, netLinker, firewallConf, routingConf, portForwardLooper,
|
allServers, ovpnConf, netLinker, firewallConf, routingConf, portForwardLooper,
|
||||||
cmder, publicIPLooper, unboundLooper, vpnLogger, httpClient,
|
cmder, publicIPLooper, unboundLooper, vpnLogger, httpClient,
|
||||||
buildInfo, *allSettings.Version.Enabled)
|
buildInfo, *allSettings.Version.Enabled)
|
||||||
vpnHandler, vpnCtx, vpnDone := goshutdown.NewGoRoutineHandler(
|
vpnHandler, vpnCtx, vpnDone := goshutdown.NewGoRoutineHandler(
|
||||||
"vpn", goroutine.OptionTimeout(time.Second))
|
"vpn", goroutine.OptionTimeout(time.Second))
|
||||||
go vpnLooper.Run(vpnCtx, vpnDone)
|
go vpnLooper.Run(vpnCtx, vpnDone)
|
||||||
|
|
||||||
updaterLooper := updater.NewLoop(allSettings.Updater,
|
updaterLooper := updater.NewLooper(allSettings.Updater,
|
||||||
providers, storage, httpClient, updaterLogger)
|
allServers, storage, vpnLooper.SetServers, httpClient,
|
||||||
|
logger.NewChild(logging.Settings{Prefix: "updater: "}))
|
||||||
updaterHandler, updaterCtx, updaterDone := goshutdown.NewGoRoutineHandler(
|
updaterHandler, updaterCtx, updaterDone := goshutdown.NewGoRoutineHandler(
|
||||||
"updater", goroutine.OptionTimeout(defaultShutdownTimeout))
|
"updater", goroutine.OptionTimeout(defaultShutdownTimeout))
|
||||||
// wait for updaterLooper.Restart() or its ticket launched with RunRestartTicker
|
// wait for updaterLooper.Restart() or its ticket launched with RunRestartTicker
|
||||||
@@ -411,36 +392,31 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
|||||||
controlGroupHandler.Add(updaterTickerHandler)
|
controlGroupHandler.Add(updaterTickerHandler)
|
||||||
|
|
||||||
httpProxyLooper := httpproxy.NewLoop(
|
httpProxyLooper := httpproxy.NewLoop(
|
||||||
logger.New(log.SetComponent("http proxy")),
|
logger.NewChild(logging.Settings{Prefix: "http proxy: "}),
|
||||||
allSettings.HTTPProxy)
|
allSettings.HTTPProxy)
|
||||||
httpProxyHandler, httpProxyCtx, httpProxyDone := goshutdown.NewGoRoutineHandler(
|
httpProxyHandler, httpProxyCtx, httpProxyDone := goshutdown.NewGoRoutineHandler(
|
||||||
"http proxy", goroutine.OptionTimeout(defaultShutdownTimeout))
|
"http proxy", goroutine.OptionTimeout(defaultShutdownTimeout))
|
||||||
go httpProxyLooper.Run(httpProxyCtx, httpProxyDone)
|
go httpProxyLooper.Run(httpProxyCtx, httpProxyDone)
|
||||||
otherGroupHandler.Add(httpProxyHandler)
|
otherGroupHandler.Add(httpProxyHandler)
|
||||||
|
|
||||||
shadowsocksLooper := shadowsocks.NewLoop(allSettings.Shadowsocks,
|
shadowsocksLooper := shadowsocks.NewLooper(allSettings.Shadowsocks,
|
||||||
logger.New(log.SetComponent("shadowsocks")))
|
logger.NewChild(logging.Settings{Prefix: "shadowsocks: "}))
|
||||||
shadowsocksHandler, shadowsocksCtx, shadowsocksDone := goshutdown.NewGoRoutineHandler(
|
shadowsocksHandler, shadowsocksCtx, shadowsocksDone := goshutdown.NewGoRoutineHandler(
|
||||||
"shadowsocks proxy", goroutine.OptionTimeout(defaultShutdownTimeout))
|
"shadowsocks proxy", goroutine.OptionTimeout(defaultShutdownTimeout))
|
||||||
go shadowsocksLooper.Run(shadowsocksCtx, shadowsocksDone)
|
go shadowsocksLooper.Run(shadowsocksCtx, shadowsocksDone)
|
||||||
otherGroupHandler.Add(shadowsocksHandler)
|
otherGroupHandler.Add(shadowsocksHandler)
|
||||||
|
|
||||||
controlServerAddress := *allSettings.ControlServer.Address
|
controlServerAddress := fmt.Sprintf(":%d", *allSettings.ControlServer.Port)
|
||||||
controlServerLogging := *allSettings.ControlServer.Log
|
controlServerLogging := *allSettings.ControlServer.Log
|
||||||
httpServerHandler, httpServerCtx, httpServerDone := goshutdown.NewGoRoutineHandler(
|
httpServerHandler, httpServerCtx, httpServerDone := goshutdown.NewGoRoutineHandler(
|
||||||
"http server", goroutine.OptionTimeout(defaultShutdownTimeout))
|
"http server", goroutine.OptionTimeout(defaultShutdownTimeout))
|
||||||
httpServer, err := server.New(httpServerCtx, controlServerAddress, controlServerLogging,
|
httpServer := server.New(httpServerCtx, controlServerAddress, controlServerLogging,
|
||||||
logger.New(log.SetComponent("http server")),
|
logger.NewChild(logging.Settings{Prefix: "http server: "}),
|
||||||
buildInfo, vpnLooper, portForwardLooper, unboundLooper, updaterLooper, publicIPLooper)
|
buildInfo, vpnLooper, portForwardLooper, unboundLooper, updaterLooper, publicIPLooper)
|
||||||
if err != nil {
|
go httpServer.Run(httpServerCtx, httpServerDone)
|
||||||
return fmt.Errorf("cannot setup control server: %w", err)
|
|
||||||
}
|
|
||||||
httpServerReady := make(chan struct{})
|
|
||||||
go httpServer.Run(httpServerCtx, httpServerReady, httpServerDone)
|
|
||||||
<-httpServerReady
|
|
||||||
controlGroupHandler.Add(httpServerHandler)
|
controlGroupHandler.Add(httpServerHandler)
|
||||||
|
|
||||||
healthLogger := logger.New(log.SetComponent("healthcheck"))
|
healthLogger := logger.NewChild(logging.Settings{Prefix: "healthcheck: "})
|
||||||
healthcheckServer := healthcheck.NewServer(allSettings.Health, healthLogger, vpnLooper)
|
healthcheckServer := healthcheck.NewServer(allSettings.Health, healthLogger, vpnLooper)
|
||||||
healthServerHandler, healthServerCtx, healthServerDone := goshutdown.NewGoRoutineHandler(
|
healthServerHandler, healthServerCtx, healthServerDone := goshutdown.NewGoRoutineHandler(
|
||||||
"HTTP health server", goroutine.OptionTimeout(defaultShutdownTimeout))
|
"HTTP health server", goroutine.OptionTimeout(defaultShutdownTimeout))
|
||||||
@@ -487,38 +463,3 @@ func printVersions(ctx context.Context, logger infoer,
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type netLinker interface {
|
|
||||||
AddrList(link netlink.Link, family int) (
|
|
||||||
addresses []netlink.Addr, err error)
|
|
||||||
AddrAdd(link netlink.Link, addr *netlink.Addr) error
|
|
||||||
IsWireguardSupported() (ok bool, err error)
|
|
||||||
RouteList(link netlink.Link, family int) (
|
|
||||||
routes []netlink.Route, err error)
|
|
||||||
RouteAdd(route *netlink.Route) error
|
|
||||||
RouteDel(route *netlink.Route) error
|
|
||||||
RouteReplace(route *netlink.Route) error
|
|
||||||
RuleList(family int) (rules []netlink.Rule, err error)
|
|
||||||
RuleAdd(rule *netlink.Rule) error
|
|
||||||
RuleDel(rule *netlink.Rule) error
|
|
||||||
LinkList() (links []netlink.Link, err error)
|
|
||||||
LinkByName(name string) (link netlink.Link, err error)
|
|
||||||
LinkByIndex(index int) (link netlink.Link, err error)
|
|
||||||
LinkAdd(link netlink.Link) (err error)
|
|
||||||
LinkDel(link netlink.Link) (err error)
|
|
||||||
LinkSetUp(link netlink.Link) (err error)
|
|
||||||
LinkSetDown(link netlink.Link) (err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type clier interface {
|
|
||||||
ClientKey(args []string) error
|
|
||||||
FormatServers(args []string) error
|
|
||||||
OpenvpnConfig(logger cli.OpenvpnConfigLogger, source sources.Source) error
|
|
||||||
HealthCheck(ctx context.Context, source sources.Source, warner cli.Warner) error
|
|
||||||
Update(ctx context.Context, args []string, logger cli.UpdaterLogger) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type Tun interface {
|
|
||||||
Check(tunDevice string) error
|
|
||||||
Create(tunDevice string) error
|
|
||||||
}
|
|
||||||
|
|||||||
10
go.mod
10
go.mod
@@ -3,7 +3,7 @@ module github.com/qdm12/gluetun
|
|||||||
go 1.17
|
go 1.17
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/breml/rootcerts v0.2.3
|
github.com/breml/rootcerts v0.2.1
|
||||||
github.com/fatih/color v1.13.0
|
github.com/fatih/color v1.13.0
|
||||||
github.com/golang/mock v1.6.0
|
github.com/golang/mock v1.6.0
|
||||||
github.com/qdm12/dns v1.11.0
|
github.com/qdm12/dns v1.11.0
|
||||||
@@ -12,13 +12,11 @@ require (
|
|||||||
github.com/qdm12/gosplash v0.1.0
|
github.com/qdm12/gosplash v0.1.0
|
||||||
github.com/qdm12/gotree v0.2.0
|
github.com/qdm12/gotree v0.2.0
|
||||||
github.com/qdm12/govalid v0.1.0
|
github.com/qdm12/govalid v0.1.0
|
||||||
github.com/qdm12/log v0.1.0
|
|
||||||
github.com/qdm12/ss-server v0.4.0
|
github.com/qdm12/ss-server v0.4.0
|
||||||
github.com/qdm12/updated v0.0.0-20210603204757-205acfe6937e
|
github.com/qdm12/updated v0.0.0-20210603204757-205acfe6937e
|
||||||
github.com/stretchr/testify v1.7.2
|
github.com/stretchr/testify v1.7.0
|
||||||
github.com/vishvananda/netlink v1.1.1-0.20211129163951-9ada19101fc5
|
github.com/vishvananda/netlink v1.1.1-0.20211129163951-9ada19101fc5
|
||||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c
|
||||||
golang.org/x/text v0.3.7
|
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20210805125648-3957e9b9dd19
|
golang.zx2c4.com/wireguard v0.0.0-20210805125648-3957e9b9dd19
|
||||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20210803171230-4253848d036c
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20210803171230-4253848d036c
|
||||||
inet.af/netaddr v0.0.0-20210718074554-06ca8145d722
|
inet.af/netaddr v0.0.0-20210718074554-06ca8145d722
|
||||||
@@ -38,8 +36,8 @@ require (
|
|||||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
|
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
|
||||||
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae // indirect
|
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae // indirect
|
||||||
go4.org/intern v0.0.0-20210108033219-3eb7198706b2 // indirect
|
go4.org/intern v0.0.0-20210108033219-3eb7198706b2 // indirect
|
||||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760 // indirect
|
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/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect
|
||||||
golang.org/x/net v0.0.0-20210504132125-bbd867fde50d // indirect
|
golang.org/x/net v0.0.0-20210504132125-bbd867fde50d // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
18
go.sum
18
go.sum
@@ -4,8 +4,8 @@ github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/g
|
|||||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
github.com/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/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/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
||||||
github.com/breml/rootcerts v0.2.3 h1:1vkYjKOiHVSyuz9Ue4AOrViEvUm8gk8phTg0vbcuU0A=
|
github.com/breml/rootcerts v0.2.1 h1:GZMVDXOs945764NFck0vtHSjktKYubOFM0kjf5HAuwc=
|
||||||
github.com/breml/rootcerts v0.2.3/go.mod h1:24FDtzYMpqIeYC7QzaE8VPRQaFZU5TIUDlyk8qwjD88=
|
github.com/breml/rootcerts v0.2.1/go.mod h1:24FDtzYMpqIeYC7QzaE8VPRQaFZU5TIUDlyk8qwjD88=
|
||||||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
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.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
@@ -115,8 +115,6 @@ github.com/qdm12/gotree v0.2.0 h1:+58ltxkNLUyHtATFereAcOjBVfY6ETqRex8XK90Fb/c=
|
|||||||
github.com/qdm12/gotree v0.2.0/go.mod h1:1SdFaqKZuI46U1apbXIf25pDMNnrPuYLEqMF/qL4lY4=
|
github.com/qdm12/gotree v0.2.0/go.mod h1:1SdFaqKZuI46U1apbXIf25pDMNnrPuYLEqMF/qL4lY4=
|
||||||
github.com/qdm12/govalid v0.1.0 h1:UIFVmuaAg0Q+h0GeyfcFEZ5sQ5KJPvRQwycC1/cqDN8=
|
github.com/qdm12/govalid v0.1.0 h1:UIFVmuaAg0Q+h0GeyfcFEZ5sQ5KJPvRQwycC1/cqDN8=
|
||||||
github.com/qdm12/govalid v0.1.0/go.mod h1:CyS/OEQdOvunBgrtIsW93fjd4jBkwZPBjGSpxq3NwA4=
|
github.com/qdm12/govalid v0.1.0/go.mod h1:CyS/OEQdOvunBgrtIsW93fjd4jBkwZPBjGSpxq3NwA4=
|
||||||
github.com/qdm12/log v0.1.0 h1:jYBd/xscHYpblzZAd2kjZp2YmuYHjAAfbTViJWxoPTw=
|
|
||||||
github.com/qdm12/log v0.1.0/go.mod h1:Vchi5M8uBvHfPNIblN4mjXn/oSbiWguQIbsgF1zdQPI=
|
|
||||||
github.com/qdm12/ss-server v0.4.0 h1:lMMYfDGc9P86Lyvd3+p8lK4hhgHUKDzjZC91FqJYkDU=
|
github.com/qdm12/ss-server v0.4.0 h1:lMMYfDGc9P86Lyvd3+p8lK4hhgHUKDzjZC91FqJYkDU=
|
||||||
github.com/qdm12/ss-server v0.4.0/go.mod h1:AY0p4huvPUPW+/CiWsJcDgT6sneDryk26VXSccPNCxY=
|
github.com/qdm12/ss-server v0.4.0/go.mod h1:AY0p4huvPUPW+/CiWsJcDgT6sneDryk26VXSccPNCxY=
|
||||||
github.com/qdm12/updated v0.0.0-20210603204757-205acfe6937e h1:4q+uFLawkaQRq3yARYLsjJPZd2wYwxn4g6G/5v0xW1g=
|
github.com/qdm12/updated v0.0.0-20210603204757-205acfe6937e h1:4q+uFLawkaQRq3yARYLsjJPZd2wYwxn4g6G/5v0xW1g=
|
||||||
@@ -130,10 +128,8 @@ github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoH
|
|||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
|
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
||||||
github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s=
|
|
||||||
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
|
|
||||||
github.com/vishvananda/netlink v1.1.1-0.20211129163951-9ada19101fc5 h1:b/k/BVWzWRS5v6AB0gf2ckFSbFsHN5jR0HoNso1pN+w=
|
github.com/vishvananda/netlink v1.1.1-0.20211129163951-9ada19101fc5 h1:b/k/BVWzWRS5v6AB0gf2ckFSbFsHN5jR0HoNso1pN+w=
|
||||||
github.com/vishvananda/netlink v1.1.1-0.20211129163951-9ada19101fc5/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
|
github.com/vishvananda/netlink v1.1.1-0.20211129163951-9ada19101fc5/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
|
||||||
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae h1:4hwBBUfQCFe3Cym0ZtKyq7L16eZUtYKs+BaHDN6mAns=
|
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae h1:4hwBBUfQCFe3Cym0ZtKyq7L16eZUtYKs+BaHDN6mAns=
|
||||||
@@ -145,9 +141,8 @@ github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1
|
|||||||
go4.org/intern v0.0.0-20210108033219-3eb7198706b2 h1:VFTf+jjIgsldaz/Mr00VaCSswHJrI2hIjQygE/W4IMg=
|
go4.org/intern v0.0.0-20210108033219-3eb7198706b2 h1:VFTf+jjIgsldaz/Mr00VaCSswHJrI2hIjQygE/W4IMg=
|
||||||
go4.org/intern v0.0.0-20210108033219-3eb7198706b2/go.mod h1:vLqJ+12kCw61iCWsPto0EOHhBS+o4rO5VIucbc9g2Cc=
|
go4.org/intern v0.0.0-20210108033219-3eb7198706b2/go.mod h1:vLqJ+12kCw61iCWsPto0EOHhBS+o4rO5VIucbc9g2Cc=
|
||||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222175341-b30ae309168e/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
|
go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222175341-b30ae309168e/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
|
||||||
|
go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222180813-1025295fd063 h1:1tk03FUNpulq2cuWpXZWj649rwJpk0d20rxWiopKRmc=
|
||||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222180813-1025295fd063/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
|
go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222180813-1025295fd063/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
|
||||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760 h1:FyBZqvoA/jbNzuAWLQE2kG820zMAkcilx6BMjGbL/E4=
|
|
||||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
|
|
||||||
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
@@ -224,8 +219,6 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
|
golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
@@ -251,9 +244,8 @@ gopkg.in/src-d/go-git.v4 v4.13.1/go.mod h1:nx5NYcxdKxq5fpltdHnPa2Exj4Sx0EclMWZQb
|
|||||||
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
||||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
inet.af/netaddr v0.0.0-20210511181906-37180328850c/go.mod h1:z0nx+Dh+7N7CC8V5ayHtHGpZpxLQZZxkIaaz6HN65Ls=
|
inet.af/netaddr v0.0.0-20210511181906-37180328850c/go.mod h1:z0nx+Dh+7N7CC8V5ayHtHGpZpxLQZZxkIaaz6HN65Ls=
|
||||||
inet.af/netaddr v0.0.0-20210718074554-06ca8145d722 h1:Qws2rZnQudC58cIagVucPQDLmMi3kAXgxscsgD0v6DU=
|
inet.af/netaddr v0.0.0-20210718074554-06ca8145d722 h1:Qws2rZnQudC58cIagVucPQDLmMi3kAXgxscsgD0v6DU=
|
||||||
inet.af/netaddr v0.0.0-20210718074554-06ca8145d722/go.mod h1:z0nx+Dh+7N7CC8V5ayHtHGpZpxLQZZxkIaaz6HN65Ls=
|
inet.af/netaddr v0.0.0-20210718074554-06ca8145d722/go.mod h1:z0nx+Dh+7N7CC8V5ayHtHGpZpxLQZZxkIaaz6HN65Ls=
|
||||||
|
|||||||
@@ -1,9 +1,17 @@
|
|||||||
|
// Package alpine defines a configurator to interact with the Alpine operating system.
|
||||||
package alpine
|
package alpine
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os/user"
|
"os/user"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var _ Alpiner = (*Alpine)(nil)
|
||||||
|
|
||||||
|
type Alpiner interface {
|
||||||
|
UserCreater
|
||||||
|
VersionGetter
|
||||||
|
}
|
||||||
|
|
||||||
type Alpine struct {
|
type Alpine struct {
|
||||||
alpineReleasePath string
|
alpineReleasePath string
|
||||||
passwdPath string
|
passwdPath string
|
||||||
|
|||||||
@@ -12,6 +12,10 @@ var (
|
|||||||
ErrUserAlreadyExists = errors.New("user already exists")
|
ErrUserAlreadyExists = errors.New("user already exists")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type UserCreater interface {
|
||||||
|
CreateUser(username string, uid int) (createdUsername string, err error)
|
||||||
|
}
|
||||||
|
|
||||||
// CreateUser creates a user in Alpine with the given UID.
|
// CreateUser creates a user in Alpine with the given UID.
|
||||||
func (a *Alpine) CreateUser(username string, uid int) (createdUsername string, err error) {
|
func (a *Alpine) CreateUser(username string, uid int) (createdUsername string, err error) {
|
||||||
UIDStr := strconv.Itoa(uid)
|
UIDStr := strconv.Itoa(uid)
|
||||||
|
|||||||
@@ -7,6 +7,10 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type VersionGetter interface {
|
||||||
|
Version(ctx context.Context) (version string, err error)
|
||||||
|
}
|
||||||
|
|
||||||
func (a *Alpine) Version(ctx context.Context) (version string, err error) {
|
func (a *Alpine) Version(ctx context.Context) (version string, err error) {
|
||||||
file, err := os.OpenFile(a.alpineReleasePath, os.O_RDONLY, 0)
|
file, err := os.OpenFile(a.alpineReleasePath, os.O_RDONLY, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -1,5 +1,16 @@
|
|||||||
|
// Package cli defines an interface CLI to run command line operations.
|
||||||
package cli
|
package cli
|
||||||
|
|
||||||
|
var _ CLIer = (*CLI)(nil)
|
||||||
|
|
||||||
|
type CLIer interface {
|
||||||
|
ClientKeyFormatter
|
||||||
|
HealthChecker
|
||||||
|
OpenvpnConfigMaker
|
||||||
|
Updater
|
||||||
|
ServersFormatter
|
||||||
|
}
|
||||||
|
|
||||||
type CLI struct {
|
type CLI struct {
|
||||||
repoServersPath string
|
repoServersPath string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,12 +7,16 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration/sources/files"
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type ClientKeyFormatter interface {
|
||||||
|
ClientKey(args []string) error
|
||||||
|
}
|
||||||
|
|
||||||
func (c *CLI) ClientKey(args []string) error {
|
func (c *CLI) ClientKey(args []string) error {
|
||||||
flagSet := flag.NewFlagSet("clientkey", flag.ExitOnError)
|
flagSet := flag.NewFlagSet("clientkey", flag.ExitOnError)
|
||||||
filepath := flagSet.String("path", files.OpenVPNClientKeyPath, "file path to the client.key file")
|
filepath := flagSet.String("path", constants.ClientKey, "file path to the client.key file")
|
||||||
if err := flagSet.Parse(args); err != nil {
|
if err := flagSet.Parse(args); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,44 +6,51 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
|
||||||
"github.com/qdm12/gluetun/internal/storage"
|
"github.com/qdm12/gluetun/internal/storage"
|
||||||
"golang.org/x/text/cases"
|
|
||||||
"golang.org/x/text/language"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type ServersFormatter interface {
|
||||||
|
FormatServers(args []string) error
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrFormatNotRecognized = errors.New("format is not recognized")
|
ErrFormatNotRecognized = errors.New("format is not recognized")
|
||||||
ErrProviderUnspecified = errors.New("VPN provider to format was not specified")
|
ErrProviderUnspecified = errors.New("VPN provider to format was not specified")
|
||||||
ErrMultipleProvidersToFormat = errors.New("more than one VPN provider to format were specified")
|
ErrOpenOutputFile = errors.New("cannot open output file")
|
||||||
|
ErrWriteOutput = errors.New("cannot write to output file")
|
||||||
|
ErrCloseOutputFile = errors.New("cannot close output file")
|
||||||
)
|
)
|
||||||
|
|
||||||
func addProviderFlag(flagSet *flag.FlagSet, providerToFormat map[string]*bool,
|
|
||||||
provider string, titleCaser cases.Caser) {
|
|
||||||
boolPtr, ok := providerToFormat[provider]
|
|
||||||
if !ok {
|
|
||||||
panic(fmt.Sprintf("unknown provider in format map: %s", provider))
|
|
||||||
}
|
|
||||||
flagSet.BoolVar(boolPtr, provider, false, "Format "+titleCaser.String(provider)+" servers")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CLI) FormatServers(args []string) error {
|
func (c *CLI) FormatServers(args []string) error {
|
||||||
var format, output string
|
var format, output string
|
||||||
allProviders := providers.All()
|
var cyberghost, expressvpn, fastestvpn, hideMyAss, ipvanish, ivpn, mullvad,
|
||||||
providersToFormat := make(map[string]*bool, len(allProviders))
|
nordvpn, perfectPrivacy, pia, privado, privatevpn, protonvpn, purevpn, surfshark,
|
||||||
for _, provider := range allProviders {
|
torguard, vpnUnlimited, vyprvpn, wevpn, windscribe bool
|
||||||
providersToFormat[provider] = new(bool)
|
|
||||||
}
|
|
||||||
flagSet := flag.NewFlagSet("markdown", flag.ExitOnError)
|
flagSet := flag.NewFlagSet("markdown", flag.ExitOnError)
|
||||||
flagSet.StringVar(&format, "format", "markdown", "Format to use which can be: 'markdown'")
|
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.StringVar(&output, "output", "/dev/stdout", "Output file to write the formatted data to")
|
||||||
titleCaser := cases.Title(language.English)
|
flagSet.BoolVar(&cyberghost, "cyberghost", false, "Format Cyberghost servers")
|
||||||
for _, provider := range allProviders {
|
flagSet.BoolVar(&expressvpn, "expressvpn", false, "Format ExpressVPN servers")
|
||||||
addProviderFlag(flagSet, providersToFormat, provider, titleCaser)
|
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 {
|
if err := flagSet.Parse(args); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -52,47 +59,74 @@ func (c *CLI) FormatServers(args []string) error {
|
|||||||
return fmt.Errorf("%w: %s", ErrFormatNotRecognized, format)
|
return fmt.Errorf("%w: %s", ErrFormatNotRecognized, format)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify only one provider is set to be formatted.
|
|
||||||
var providers []string
|
|
||||||
for provider, formatPtr := range providersToFormat {
|
|
||||||
if *formatPtr {
|
|
||||||
providers = append(providers, provider)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
switch len(providers) {
|
|
||||||
case 0:
|
|
||||||
return ErrProviderUnspecified
|
|
||||||
case 1:
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("%w: %d specified: %s",
|
|
||||||
ErrMultipleProvidersToFormat, len(providers),
|
|
||||||
strings.Join(providers, ", "))
|
|
||||||
}
|
|
||||||
providerToFormat := providers[0]
|
|
||||||
|
|
||||||
logger := newNoopLogger()
|
logger := newNoopLogger()
|
||||||
storage, err := storage.New(logger, constants.ServersData)
|
storage, err := storage.New(logger, constants.ServersData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot create servers storage: %w", err)
|
return fmt.Errorf("%w: %s", ErrNewStorage, err)
|
||||||
}
|
}
|
||||||
|
currentServers := storage.GetServers()
|
||||||
|
|
||||||
formatted := storage.FormatToMarkdown(providerToFormat)
|
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)
|
output = filepath.Clean(output)
|
||||||
file, err := os.OpenFile(output, os.O_TRUNC|os.O_WRONLY|os.O_CREATE, 0644)
|
file, err := os.OpenFile(output, os.O_TRUNC|os.O_WRONLY|os.O_CREATE, 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot open output file: %w", err)
|
return fmt.Errorf("%w: %s", ErrOpenOutputFile, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = fmt.Fprint(file, formatted)
|
_, err = fmt.Fprint(file, formatted)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = file.Close()
|
_ = file.Close()
|
||||||
return fmt.Errorf("cannot write to output file: %w", err)
|
return fmt.Errorf("%w: %s", ErrWriteOutput, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = file.Close()
|
err = file.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot close output file: %w", err)
|
return fmt.Errorf("%w: %s", ErrCloseOutputFile, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -10,6 +10,10 @@ import (
|
|||||||
"github.com/qdm12/gluetun/internal/healthcheck"
|
"github.com/qdm12/gluetun/internal/healthcheck"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type HealthChecker interface {
|
||||||
|
HealthCheck(ctx context.Context, source sources.Source, warner Warner) error
|
||||||
|
}
|
||||||
|
|
||||||
func (c *CLI) HealthCheck(ctx context.Context, source sources.Source, warner Warner) error {
|
func (c *CLI) HealthCheck(ctx context.Context, source sources.Source, warner Warner) error {
|
||||||
// Extract the health server port from the configuration.
|
// Extract the health server port from the configuration.
|
||||||
config, err := source.ReadHealth()
|
config, err := source.ReadHealth()
|
||||||
|
|||||||
@@ -1,73 +1,50 @@
|
|||||||
package cli
|
package cli
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration/sources"
|
"github.com/qdm12/gluetun/internal/configuration/sources"
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
"github.com/qdm12/gluetun/internal/openvpn/extract"
|
|
||||||
"github.com/qdm12/gluetun/internal/provider"
|
"github.com/qdm12/gluetun/internal/provider"
|
||||||
"github.com/qdm12/gluetun/internal/publicip/ipinfo"
|
|
||||||
"github.com/qdm12/gluetun/internal/storage"
|
"github.com/qdm12/gluetun/internal/storage"
|
||||||
"github.com/qdm12/gluetun/internal/updater/resolver"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type OpenvpnConfigMaker interface {
|
||||||
|
OpenvpnConfig(logger OpenvpnConfigLogger, source sources.Source) error
|
||||||
|
}
|
||||||
|
|
||||||
type OpenvpnConfigLogger interface {
|
type OpenvpnConfigLogger interface {
|
||||||
Info(s string)
|
Info(s string)
|
||||||
Warn(s string)
|
Warn(s string)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Unzipper interface {
|
|
||||||
FetchAndExtract(ctx context.Context, url string) (
|
|
||||||
contents map[string][]byte, err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type ParallelResolver interface {
|
|
||||||
Resolve(ctx context.Context, settings resolver.ParallelSettings) (
|
|
||||||
hostToIPs map[string][]net.IP, warnings []string, err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type IPFetcher interface {
|
|
||||||
FetchMultiInfo(ctx context.Context, ips []net.IP) (data []ipinfo.Response, err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CLI) OpenvpnConfig(logger OpenvpnConfigLogger, source sources.Source) error {
|
func (c *CLI) OpenvpnConfig(logger OpenvpnConfigLogger, source sources.Source) error {
|
||||||
storage, err := storage.New(logger, constants.ServersData)
|
storage, err := storage.New(logger, constants.ServersData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
allServers := storage.GetServers()
|
||||||
|
|
||||||
allSettings, err := source.Read()
|
allSettings, err := source.Read()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = allSettings.Validate(storage); err != nil {
|
if err = allSettings.Validate(allServers); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unused by this CLI command
|
providerConf := provider.New(*allSettings.VPN.Provider.Name, allServers, time.Now)
|
||||||
unzipper := (Unzipper)(nil)
|
|
||||||
client := (*http.Client)(nil)
|
|
||||||
warner := (Warner)(nil)
|
|
||||||
parallelResolver := (ParallelResolver)(nil)
|
|
||||||
ipFetcher := (IPFetcher)(nil)
|
|
||||||
openvpnFileExtractor := extract.New()
|
|
||||||
|
|
||||||
providers := provider.NewProviders(storage, time.Now, warner, client,
|
|
||||||
unzipper, parallelResolver, ipFetcher, openvpnFileExtractor)
|
|
||||||
providerConf := providers.Get(*allSettings.VPN.Provider.Name)
|
|
||||||
connection, err := providerConf.GetConnection(allSettings.VPN.Provider.ServerSelection)
|
connection, err := providerConf.GetConnection(allSettings.VPN.Provider.ServerSelection)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
lines, err := providerConf.BuildConf(connection, allSettings.VPN.OpenVPN)
|
||||||
lines := providerConf.OpenVPNConfig(connection, allSettings.VPN.OpenVPN)
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
fmt.Println(strings.Join(lines, "\n"))
|
fmt.Println(strings.Join(lines, "\n"))
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -2,48 +2,53 @@ package cli
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
"github.com/qdm12/gluetun/internal/openvpn/extract"
|
|
||||||
"github.com/qdm12/gluetun/internal/provider"
|
|
||||||
"github.com/qdm12/gluetun/internal/publicip/ipinfo"
|
|
||||||
"github.com/qdm12/gluetun/internal/storage"
|
"github.com/qdm12/gluetun/internal/storage"
|
||||||
"github.com/qdm12/gluetun/internal/updater"
|
"github.com/qdm12/gluetun/internal/updater"
|
||||||
"github.com/qdm12/gluetun/internal/updater/resolver"
|
|
||||||
"github.com/qdm12/gluetun/internal/updater/unzip"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrModeUnspecified = errors.New("at least one of -enduser or -maintainer must be specified")
|
ErrModeUnspecified = errors.New("at least one of -enduser or -maintainer must be specified")
|
||||||
ErrNoProviderSpecified = errors.New("no provider was specified")
|
ErrDNSAddress = errors.New("DNS address is not valid")
|
||||||
|
ErrNoProviderSpecified = errors.New("no provider was specified")
|
||||||
|
ErrNewStorage = errors.New("cannot create storage")
|
||||||
|
ErrUpdateServerInformation = errors.New("cannot update server information")
|
||||||
|
ErrWriteToFile = errors.New("cannot write updated information to file")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Updater interface {
|
||||||
|
Update(ctx context.Context, args []string, logger UpdaterLogger) error
|
||||||
|
}
|
||||||
|
|
||||||
type UpdaterLogger interface {
|
type UpdaterLogger interface {
|
||||||
Info(s string)
|
Info(s string)
|
||||||
Warn(s string)
|
Warn(s string)
|
||||||
Error(s string)
|
Error(s string)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func boolPtr(b bool) *bool { return &b }
|
||||||
|
|
||||||
func (c *CLI) Update(ctx context.Context, args []string, logger UpdaterLogger) error {
|
func (c *CLI) Update(ctx context.Context, args []string, logger UpdaterLogger) error {
|
||||||
options := settings.Updater{}
|
options := settings.Updater{CLI: boolPtr(true)}
|
||||||
var endUserMode, maintainerMode, updateAll bool
|
var endUserMode, maintainerMode, updateAll bool
|
||||||
var csvProviders string
|
var dnsAddress, csvProviders string
|
||||||
flagSet := flag.NewFlagSet("update", flag.ExitOnError)
|
flagSet := flag.NewFlagSet("update", flag.ExitOnError)
|
||||||
flagSet.BoolVar(&endUserMode, "enduser", false, "Write results to /gluetun/servers.json (for end users)")
|
flagSet.BoolVar(&endUserMode, "enduser", false, "Write results to /gluetun/servers.json (for end users)")
|
||||||
flagSet.BoolVar(&maintainerMode, "maintainer", false,
|
flagSet.BoolVar(&maintainerMode, "maintainer", false,
|
||||||
"Write results to ./internal/storage/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.StringVar(&dnsAddress, "dns", "8.8.8.8", "DNS resolver address to use")
|
||||||
const defaultMinRatio = 0.8
|
|
||||||
flagSet.Float64Var(&options.MinRatio, "minratio", defaultMinRatio,
|
|
||||||
"Minimum ratio of servers to find for the update to succeed")
|
|
||||||
flagSet.BoolVar(&updateAll, "all", false, "Update servers for all VPN providers")
|
flagSet.BoolVar(&updateAll, "all", false, "Update servers for all VPN providers")
|
||||||
flagSet.StringVar(&csvProviders, "providers", "", "CSV string of VPN providers to update server data for")
|
flagSet.StringVar(&csvProviders, "providers", "", "CSV string of VPN providers to update server data for")
|
||||||
if err := flagSet.Parse(args); err != nil {
|
if err := flagSet.Parse(args); err != nil {
|
||||||
@@ -54,8 +59,18 @@ func (c *CLI) Update(ctx context.Context, args []string, logger UpdaterLogger) e
|
|||||||
return ErrModeUnspecified
|
return ErrModeUnspecified
|
||||||
}
|
}
|
||||||
|
|
||||||
|
options.DNSAddress = net.ParseIP(dnsAddress)
|
||||||
|
if options.DNSAddress == nil {
|
||||||
|
return fmt.Errorf("%w: %s", ErrDNSAddress, dnsAddress)
|
||||||
|
}
|
||||||
|
|
||||||
if updateAll {
|
if updateAll {
|
||||||
options.Providers = providers.All()
|
for _, provider := range constants.AllProviders() {
|
||||||
|
if provider == constants.Custom {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
options.Providers = append(options.Providers, provider)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if csvProviders == "" {
|
if csvProviders == "" {
|
||||||
return ErrNoProviderSpecified
|
return ErrNoProviderSpecified
|
||||||
@@ -63,40 +78,55 @@ func (c *CLI) Update(ctx context.Context, args []string, logger UpdaterLogger) e
|
|||||||
options.Providers = strings.Split(csvProviders, ",")
|
options.Providers = strings.Split(csvProviders, ",")
|
||||||
}
|
}
|
||||||
|
|
||||||
options.SetDefaults(options.Providers[0])
|
options.SetDefaults()
|
||||||
|
|
||||||
err := options.Validate()
|
err := options.Validate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("options validation failed: %w", err)
|
return fmt.Errorf("options validation failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
storage, err := storage.New(logger, constants.ServersData)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("cannot create servers storage: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
const clientTimeout = 10 * time.Second
|
const clientTimeout = 10 * time.Second
|
||||||
httpClient := &http.Client{Timeout: clientTimeout}
|
httpClient := &http.Client{Timeout: clientTimeout}
|
||||||
unzipper := unzip.New(httpClient)
|
|
||||||
parallelResolver := resolver.NewParallelResolver(options.DNSAddress)
|
|
||||||
ipFetcher := ipinfo.New(httpClient)
|
|
||||||
openvpnFileExtractor := extract.New()
|
|
||||||
|
|
||||||
providers := provider.NewProviders(storage, time.Now, logger, httpClient,
|
storage, err := storage.New(logger, constants.ServersData)
|
||||||
unzipper, parallelResolver, ipFetcher, openvpnFileExtractor)
|
|
||||||
|
|
||||||
updater := updater.New(httpClient, storage, providers, logger)
|
|
||||||
err = updater.UpdateServers(ctx, options.Providers, options.MinRatio)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot update server information: %w", err)
|
return fmt.Errorf("%w: %s", ErrNewStorage, err)
|
||||||
|
}
|
||||||
|
currentServers := storage.GetServers()
|
||||||
|
|
||||||
|
updater := updater.New(options, httpClient, currentServers, logger)
|
||||||
|
allServers, err := updater.UpdateServers(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%w: %s", ErrUpdateServerInformation, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if endUserMode {
|
||||||
|
if err := storage.FlushToFile(allServers); err != nil {
|
||||||
|
return fmt.Errorf("%w: %s", ErrWriteToFile, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if maintainerMode {
|
if maintainerMode {
|
||||||
err := storage.FlushToFile(c.repoServersPath)
|
if err := writeToEmbeddedJSON(c.repoServersPath, allServers); err != nil {
|
||||||
if err != nil {
|
return fmt.Errorf("%w: %s", ErrWriteToFile, err)
|
||||||
return fmt.Errorf("cannot write servers data to embedded JSON file: %w", err)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func writeToEmbeddedJSON(repoServersPath string,
|
||||||
|
allServers models.AllServers) error {
|
||||||
|
const perms = 0600
|
||||||
|
f, err := os.OpenFile(repoServersPath,
|
||||||
|
os.O_TRUNC|os.O_WRONLY|os.O_CREATE, perms)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
encoder := json.NewEncoder(f)
|
||||||
|
encoder.SetIndent("", " ")
|
||||||
|
return encoder.Encode(allServers)
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,14 +6,15 @@ var (
|
|||||||
ErrCityNotValid = errors.New("the city specified is not valid")
|
ErrCityNotValid = errors.New("the city specified is not valid")
|
||||||
ErrControlServerPrivilegedPort = errors.New("cannot use privileged port without running as root")
|
ErrControlServerPrivilegedPort = errors.New("cannot use privileged port without running as root")
|
||||||
ErrCountryNotValid = errors.New("the country specified is not valid")
|
ErrCountryNotValid = errors.New("the country specified is not valid")
|
||||||
ErrFilepathMissing = errors.New("filepath is missing")
|
|
||||||
ErrFirewallZeroPort = errors.New("cannot have a zero port to block")
|
ErrFirewallZeroPort = errors.New("cannot have a zero port to block")
|
||||||
ErrHostnameNotValid = errors.New("the hostname specified is not valid")
|
ErrHostnameNotValid = errors.New("the hostname specified is not valid")
|
||||||
ErrISPNotValid = errors.New("the ISP specified is not valid")
|
ErrISPNotValid = errors.New("the ISP specified is not valid")
|
||||||
ErrMinRatioNotValid = errors.New("minimum ratio is not valid")
|
|
||||||
ErrMissingValue = errors.New("missing value")
|
|
||||||
ErrNameNotValid = errors.New("the server name specified is not valid")
|
ErrNameNotValid = errors.New("the server name specified is not valid")
|
||||||
|
ErrOpenVPNClientCertMissing = errors.New("client certificate is missing")
|
||||||
|
ErrOpenVPNClientCertNotValid = errors.New("client certificate is not valid")
|
||||||
ErrOpenVPNClientKeyMissing = errors.New("client key is missing")
|
ErrOpenVPNClientKeyMissing = errors.New("client key is missing")
|
||||||
|
ErrOpenVPNClientKeyNotValid = errors.New("client key is not valid")
|
||||||
|
ErrOpenVPNConfigFile = errors.New("custom configuration file error")
|
||||||
ErrOpenVPNCustomPortNotAllowed = errors.New("custom endpoint port is not allowed")
|
ErrOpenVPNCustomPortNotAllowed = errors.New("custom endpoint port is not allowed")
|
||||||
ErrOpenVPNEncryptionPresetNotValid = errors.New("PIA encryption preset is not valid")
|
ErrOpenVPNEncryptionPresetNotValid = errors.New("PIA encryption preset is not valid")
|
||||||
ErrOpenVPNInterfaceNotValid = errors.New("interface name is not valid")
|
ErrOpenVPNInterfaceNotValid = errors.New("interface name is not valid")
|
||||||
@@ -24,13 +25,14 @@ var (
|
|||||||
ErrOpenVPNVerbosityIsOutOfBounds = errors.New("verbosity value is out of bounds")
|
ErrOpenVPNVerbosityIsOutOfBounds = errors.New("verbosity value is out of bounds")
|
||||||
ErrOpenVPNVersionIsNotValid = errors.New("version is not valid")
|
ErrOpenVPNVersionIsNotValid = errors.New("version is not valid")
|
||||||
ErrPortForwardingEnabled = errors.New("port forwarding cannot be enabled")
|
ErrPortForwardingEnabled = errors.New("port forwarding cannot be enabled")
|
||||||
|
ErrPortForwardingFilepathNotValid = errors.New("port forwarding filepath given is not valid")
|
||||||
|
ErrPublicIPFilepathNotValid = errors.New("public IP address file path is not valid")
|
||||||
ErrPublicIPPeriodTooShort = errors.New("public IP address check period is too short")
|
ErrPublicIPPeriodTooShort = errors.New("public IP address check period is too short")
|
||||||
ErrRegionNotValid = errors.New("the region specified is not valid")
|
ErrRegionNotValid = errors.New("the region specified is not valid")
|
||||||
ErrServerAddressNotValid = errors.New("server listening address is not valid")
|
ErrServerAddressNotValid = errors.New("server listening address is not valid")
|
||||||
ErrSystemPGIDNotValid = errors.New("process group id is not valid")
|
ErrSystemPGIDNotValid = errors.New("process group id is not valid")
|
||||||
ErrSystemPUIDNotValid = errors.New("process user id is not valid")
|
ErrSystemPUIDNotValid = errors.New("process user id is not valid")
|
||||||
ErrSystemTimezoneNotValid = errors.New("timezone is not valid")
|
ErrSystemTimezoneNotValid = errors.New("timezone is not valid")
|
||||||
ErrUpdaterPeriodTooSmall = errors.New("VPN server data updater period is too small")
|
|
||||||
ErrVPNProviderNameNotValid = errors.New("VPN provider name is not valid")
|
ErrVPNProviderNameNotValid = errors.New("VPN provider name is not valid")
|
||||||
ErrVPNTypeNotValid = errors.New("VPN type is not valid")
|
ErrVPNTypeNotValid = errors.New("VPN type is not valid")
|
||||||
ErrWireguardEndpointIPNotSet = errors.New("endpoint IP is not set")
|
ErrWireguardEndpointIPNotSet = errors.New("endpoint IP is not set")
|
||||||
@@ -39,7 +41,11 @@ var (
|
|||||||
ErrWireguardInterfaceAddressNotSet = errors.New("interface address is not set")
|
ErrWireguardInterfaceAddressNotSet = errors.New("interface address is not set")
|
||||||
ErrWireguardInterfaceNotValid = errors.New("interface name is not valid")
|
ErrWireguardInterfaceNotValid = errors.New("interface name is not valid")
|
||||||
ErrWireguardPreSharedKeyNotSet = errors.New("pre-shared key is not set")
|
ErrWireguardPreSharedKeyNotSet = errors.New("pre-shared key is not set")
|
||||||
|
ErrWireguardPreSharedKeyNotValid = errors.New("pre-shared key is not valid")
|
||||||
ErrWireguardPrivateKeyNotSet = errors.New("private key is not set")
|
ErrWireguardPrivateKeyNotSet = errors.New("private key is not set")
|
||||||
|
ErrWireguardPrivateKeyNotValid = errors.New("private key is not valid")
|
||||||
ErrWireguardPublicKeyNotSet = errors.New("public key is not set")
|
ErrWireguardPublicKeyNotSet = errors.New("public key is not set")
|
||||||
ErrWireguardPublicKeyNotValid = errors.New("public key is not valid")
|
ErrWireguardPublicKeyNotValid = errors.New("public key is not valid")
|
||||||
|
|
||||||
|
ErrUpdaterPeriodTooSmall = errors.New("VPN server data updater period is too small")
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -27,12 +27,13 @@ func (h Health) Validate() (err error) {
|
|||||||
_, err = address.Validate(h.ServerAddress,
|
_, err = address.Validate(h.ServerAddress,
|
||||||
address.OptionListening(uid))
|
address.OptionListening(uid))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("server listening address is not valid: %w", err)
|
return fmt.Errorf("%w: %s",
|
||||||
|
ErrServerAddressNotValid, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = h.VPN.validate()
|
err = h.VPN.validate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("health VPN settings: %w", err)
|
return fmt.Errorf("health VPN settings validation failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -65,7 +66,7 @@ func (h *Health) OverrideWith(other Health) {
|
|||||||
|
|
||||||
func (h *Health) SetDefaults() {
|
func (h *Health) SetDefaults() {
|
||||||
h.ServerAddress = helpers.DefaultString(h.ServerAddress, "127.0.0.1:9999")
|
h.ServerAddress = helpers.DefaultString(h.ServerAddress, "127.0.0.1:9999")
|
||||||
h.TargetAddress = helpers.DefaultString(h.TargetAddress, "cloudflare.com:443")
|
h.TargetAddress = helpers.DefaultString(h.TargetAddress, "github.com:443")
|
||||||
h.VPN.setDefaults()
|
h.VPN.setDefaults()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,16 +15,9 @@ func IsOneOf(value string, choices ...string) (ok bool) {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var ErrValueNotOneOf = errors.New("value is not one of the possible choices")
|
||||||
ErrNoChoice = errors.New("one or more values is set but there is no possible value available")
|
|
||||||
ErrValueNotOneOf = errors.New("value is not one of the possible choices")
|
|
||||||
)
|
|
||||||
|
|
||||||
func AreAllOneOf(values, choices []string) (err error) {
|
func AreAllOneOf(values, choices []string) (err error) {
|
||||||
if len(values) > 0 && len(choices) == 0 {
|
|
||||||
return ErrNoChoice
|
|
||||||
}
|
|
||||||
|
|
||||||
set := make(map[string]struct{}, len(choices))
|
set := make(map[string]struct{}, len(choices))
|
||||||
for _, choice := range choices {
|
for _, choice := range choices {
|
||||||
choice = strings.ToLower(choice)
|
choice = strings.ToLower(choice)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/qdm12/log"
|
"github.com/qdm12/golibs/logging"
|
||||||
"inet.af/netaddr"
|
"inet.af/netaddr"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -44,15 +44,6 @@ func CopyUint16Ptr(original *uint16) (copied *uint16) {
|
|||||||
return copied
|
return copied
|
||||||
}
|
}
|
||||||
|
|
||||||
func CopyUint32Ptr(original *uint32) (copied *uint32) {
|
|
||||||
if original == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
copied = new(uint32)
|
|
||||||
*copied = *original
|
|
||||||
return copied
|
|
||||||
}
|
|
||||||
|
|
||||||
func CopyIntPtr(original *int) (copied *int) {
|
func CopyIntPtr(original *int) (copied *int) {
|
||||||
if original == nil {
|
if original == nil {
|
||||||
return nil
|
return nil
|
||||||
@@ -71,11 +62,11 @@ func CopyDurationPtr(original *time.Duration) (copied *time.Duration) {
|
|||||||
return copied
|
return copied
|
||||||
}
|
}
|
||||||
|
|
||||||
func CopyLogLevelPtr(original *log.Level) (copied *log.Level) {
|
func CopyLogLevelPtr(original *logging.Level) (copied *logging.Level) {
|
||||||
if original == nil {
|
if original == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
copied = new(log.Level)
|
copied = new(logging.Level)
|
||||||
*copied = *original
|
*copied = *original
|
||||||
return copied
|
return copied
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/qdm12/log"
|
"github.com/qdm12/golibs/logging"
|
||||||
)
|
)
|
||||||
|
|
||||||
func DefaultInt(existing *int, defaultValue int) (
|
func DefaultInt(existing *int, defaultValue int) (
|
||||||
@@ -36,15 +36,6 @@ func DefaultUint16(existing *uint16, defaultValue uint16) (
|
|||||||
*result = defaultValue
|
*result = defaultValue
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
func DefaultUint32(existing *uint32, defaultValue uint32) (
|
|
||||||
result *uint32) {
|
|
||||||
if existing != nil {
|
|
||||||
return existing
|
|
||||||
}
|
|
||||||
result = new(uint32)
|
|
||||||
*result = defaultValue
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func DefaultBool(existing *bool, defaultValue bool) (
|
func DefaultBool(existing *bool, defaultValue bool) (
|
||||||
result *bool) {
|
result *bool) {
|
||||||
@@ -83,12 +74,12 @@ func DefaultDuration(existing *time.Duration,
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func DefaultLogLevel(existing *log.Level,
|
func DefaultLogLevel(existing *logging.Level,
|
||||||
defaultValue log.Level) (result *log.Level) {
|
defaultValue logging.Level) (result *logging.Level) {
|
||||||
if existing != nil {
|
if existing != nil {
|
||||||
return existing
|
return existing
|
||||||
}
|
}
|
||||||
result = new(log.Level)
|
result = new(logging.Level)
|
||||||
*result = defaultValue
|
*result = defaultValue
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,10 +2,9 @@ package helpers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/qdm12/log"
|
"github.com/qdm12/golibs/logging"
|
||||||
"inet.af/netaddr"
|
"inet.af/netaddr"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -27,20 +26,6 @@ func MergeWithString(existing, other string) (result string) {
|
|||||||
return other
|
return other
|
||||||
}
|
}
|
||||||
|
|
||||||
func MergeWithInt(existing, other int) (result int) {
|
|
||||||
if existing != 0 {
|
|
||||||
return existing
|
|
||||||
}
|
|
||||||
return other
|
|
||||||
}
|
|
||||||
|
|
||||||
func MergeWithFloat64(existing, other float64) (result float64) {
|
|
||||||
if existing != 0 {
|
|
||||||
return existing
|
|
||||||
}
|
|
||||||
return other
|
|
||||||
}
|
|
||||||
|
|
||||||
func MergeWithStringPtr(existing, other *string) (result *string) {
|
func MergeWithStringPtr(existing, other *string) (result *string) {
|
||||||
if existing != nil {
|
if existing != nil {
|
||||||
return existing
|
return existing
|
||||||
@@ -52,7 +37,7 @@ func MergeWithStringPtr(existing, other *string) (result *string) {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func MergeWithIntPtr(existing, other *int) (result *int) {
|
func MergeWithInt(existing, other *int) (result *int) {
|
||||||
if existing != nil {
|
if existing != nil {
|
||||||
return existing
|
return existing
|
||||||
} else if other == nil {
|
} else if other == nil {
|
||||||
@@ -85,17 +70,6 @@ func MergeWithUint16(existing, other *uint16) (result *uint16) {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func MergeWithUint32(existing, other *uint32) (result *uint32) {
|
|
||||||
if existing != nil {
|
|
||||||
return existing
|
|
||||||
} else if other == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
result = new(uint32)
|
|
||||||
*result = *other
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func MergeWithIP(existing, other net.IP) (result net.IP) {
|
func MergeWithIP(existing, other net.IP) (result net.IP) {
|
||||||
if existing != nil {
|
if existing != nil {
|
||||||
return existing
|
return existing
|
||||||
@@ -114,24 +88,17 @@ func MergeWithDuration(existing, other *time.Duration) (result *time.Duration) {
|
|||||||
return other
|
return other
|
||||||
}
|
}
|
||||||
|
|
||||||
func MergeWithLogLevel(existing, other *log.Level) (result *log.Level) {
|
func MergeWithLogLevel(existing, other *logging.Level) (result *logging.Level) {
|
||||||
if existing != nil {
|
if existing != nil {
|
||||||
return existing
|
return existing
|
||||||
} else if other == nil {
|
} else if other == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
result = new(log.Level)
|
result = new(logging.Level)
|
||||||
*result = *other
|
*result = *other
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func MergeWithHTTPHandler(existing, other http.Handler) (result http.Handler) {
|
|
||||||
if existing != nil {
|
|
||||||
return existing
|
|
||||||
}
|
|
||||||
return other
|
|
||||||
}
|
|
||||||
|
|
||||||
func MergeStringSlices(a, b []string) (result []string) {
|
func MergeStringSlices(a, b []string) (result []string) {
|
||||||
if a == nil && b == nil {
|
if a == nil && b == nil {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -2,10 +2,9 @@ package helpers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/qdm12/log"
|
"github.com/qdm12/golibs/logging"
|
||||||
"inet.af/netaddr"
|
"inet.af/netaddr"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -25,20 +24,6 @@ func OverrideWithString(existing, other string) (result string) {
|
|||||||
return other
|
return other
|
||||||
}
|
}
|
||||||
|
|
||||||
func OverrideWithInt(existing, other int) (result int) {
|
|
||||||
if other == 0 {
|
|
||||||
return existing
|
|
||||||
}
|
|
||||||
return other
|
|
||||||
}
|
|
||||||
|
|
||||||
func OverrideWithFloat64(existing, other float64) (result float64) {
|
|
||||||
if other == 0 {
|
|
||||||
return existing
|
|
||||||
}
|
|
||||||
return other
|
|
||||||
}
|
|
||||||
|
|
||||||
func OverrideWithStringPtr(existing, other *string) (result *string) {
|
func OverrideWithStringPtr(existing, other *string) (result *string) {
|
||||||
if other == nil {
|
if other == nil {
|
||||||
return existing
|
return existing
|
||||||
@@ -48,7 +33,7 @@ func OverrideWithStringPtr(existing, other *string) (result *string) {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func OverrideWithIntPtr(existing, other *int) (result *int) {
|
func OverrideWithInt(existing, other *int) (result *int) {
|
||||||
if other == nil {
|
if other == nil {
|
||||||
return existing
|
return existing
|
||||||
}
|
}
|
||||||
@@ -75,15 +60,6 @@ func OverrideWithUint16(existing, other *uint16) (result *uint16) {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func OverrideWithUint32(existing, other *uint32) (result *uint32) {
|
|
||||||
if other == nil {
|
|
||||||
return existing
|
|
||||||
}
|
|
||||||
result = new(uint32)
|
|
||||||
*result = *other
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func OverrideWithIP(existing, other net.IP) (result net.IP) {
|
func OverrideWithIP(existing, other net.IP) (result net.IP) {
|
||||||
if other == nil {
|
if other == nil {
|
||||||
return existing
|
return existing
|
||||||
@@ -102,22 +78,15 @@ func OverrideWithDuration(existing, other *time.Duration) (result *time.Duration
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func OverrideWithLogLevel(existing, other *log.Level) (result *log.Level) {
|
func OverrideWithLogLevel(existing, other *logging.Level) (result *logging.Level) {
|
||||||
if other == nil {
|
if other == nil {
|
||||||
return existing
|
return existing
|
||||||
}
|
}
|
||||||
result = new(log.Level)
|
result = new(logging.Level)
|
||||||
*result = *other
|
*result = *other
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func OverrideWithHTTPHandler(existing, other http.Handler) (result http.Handler) {
|
|
||||||
if other != nil {
|
|
||||||
return other
|
|
||||||
}
|
|
||||||
return existing
|
|
||||||
}
|
|
||||||
|
|
||||||
func OverrideWithStringSlice(existing, other []string) (result []string) {
|
func OverrideWithStringSlice(existing, other []string) (result []string) {
|
||||||
if other == nil {
|
if other == nil {
|
||||||
return existing
|
return existing
|
||||||
|
|||||||
@@ -41,7 +41,8 @@ func (h HTTPProxy) validate() (err error) {
|
|||||||
uid := os.Getuid()
|
uid := os.Getuid()
|
||||||
_, err = address.Validate(h.ListeningAddress, address.OptionListening(uid))
|
_, err = address.Validate(h.ListeningAddress, address.OptionListening(uid))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%w: %s", ErrServerAddressNotValid, h.ListeningAddress)
|
return fmt.Errorf("%w: %s",
|
||||||
|
ErrServerAddressNotValid, h.ListeningAddress)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -2,15 +2,15 @@ package settings
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||||
|
"github.com/qdm12/golibs/logging"
|
||||||
"github.com/qdm12/gotree"
|
"github.com/qdm12/gotree"
|
||||||
"github.com/qdm12/log"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Log contains settings to configure the logger.
|
// Log contains settings to configure the logger.
|
||||||
type Log struct {
|
type Log struct {
|
||||||
// Level is the log level of the logger.
|
// Level is the log level of the logger.
|
||||||
// It cannot be nil in the internal state.
|
// It cannot be nil in the internal state.
|
||||||
Level *log.Level
|
Level *logging.Level
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l Log) validate() (err error) {
|
func (l Log) validate() (err error) {
|
||||||
@@ -37,7 +37,7 @@ func (l *Log) overrideWith(other Log) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (l *Log) setDefaults() {
|
func (l *Log) setDefaults() {
|
||||||
l.Level = helpers.DefaultLogLevel(l.Level, log.LevelInfo)
|
l.Level = helpers.DefaultLogLevel(l.Level, logging.LevelInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l Log) String() string {
|
func (l Log) String() string {
|
||||||
|
|||||||
@@ -1,16 +1,12 @@
|
|||||||
package settings
|
package settings
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||||
"github.com/qdm12/gluetun/internal/constants/openvpn"
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
"github.com/qdm12/gluetun/internal/openvpn/parse"
|
||||||
"github.com/qdm12/gluetun/internal/openvpn/extract"
|
|
||||||
"github.com/qdm12/gluetun/internal/provider/privateinternetaccess/presets"
|
|
||||||
"github.com/qdm12/gotree"
|
"github.com/qdm12/gotree"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -65,10 +61,15 @@ type OpenVPN struct {
|
|||||||
// Interface is the OpenVPN device interface name.
|
// Interface is the OpenVPN device interface name.
|
||||||
// It cannot be an empty string in the internal state.
|
// It cannot be an empty string in the internal state.
|
||||||
Interface string
|
Interface string
|
||||||
// ProcessUser is the OpenVPN process OS username
|
// Root is true if OpenVPN is to be run as root,
|
||||||
// to use. It cannot be empty in the internal state.
|
// and false otherwise. It cannot be nil in the
|
||||||
// It defaults to 'root'.
|
// internal state.
|
||||||
ProcessUser string
|
Root *bool
|
||||||
|
// ProcUser is the OpenVPN process OS username
|
||||||
|
// to use. It cannot be nil in the internal state.
|
||||||
|
// This is set and injected at runtime.
|
||||||
|
// TODO only use ProcUser and not Root field.
|
||||||
|
ProcUser string
|
||||||
// Verbosity is the OpenVPN verbosity level from 0 to 6.
|
// Verbosity is the OpenVPN verbosity level from 0 to 6.
|
||||||
// It cannot be nil in the internal state.
|
// It cannot be nil in the internal state.
|
||||||
Verbosity *int
|
Verbosity *int
|
||||||
@@ -77,44 +78,69 @@ type OpenVPN struct {
|
|||||||
Flags []string
|
Flags []string
|
||||||
}
|
}
|
||||||
|
|
||||||
var ivpnAccountID = regexp.MustCompile(`^(i|ivpn)\-[a-zA-Z0-9]{4}\-[a-zA-Z0-9]{4}\-[a-zA-Z0-9]{4}$`)
|
|
||||||
|
|
||||||
func (o OpenVPN) validate(vpnProvider string) (err error) {
|
func (o OpenVPN) validate(vpnProvider string) (err error) {
|
||||||
// Validate version
|
// Validate version
|
||||||
validVersions := []string{openvpn.Openvpn24, openvpn.Openvpn25}
|
validVersions := []string{constants.Openvpn24, constants.Openvpn25}
|
||||||
if !helpers.IsOneOf(o.Version, validVersions...) {
|
if !helpers.IsOneOf(o.Version, validVersions...) {
|
||||||
return fmt.Errorf("%w: %q can only be one of %s",
|
return fmt.Errorf("%w: %q can only be one of %s",
|
||||||
ErrOpenVPNVersionIsNotValid, o.Version, strings.Join(validVersions, ", "))
|
ErrOpenVPNVersionIsNotValid, o.Version, strings.Join(validVersions, ", "))
|
||||||
}
|
}
|
||||||
|
|
||||||
isCustom := vpnProvider == providers.Custom
|
isCustom := vpnProvider == constants.Custom
|
||||||
|
|
||||||
if !isCustom && o.User == "" {
|
if !isCustom && o.User == "" {
|
||||||
return ErrOpenVPNUserIsEmpty
|
return ErrOpenVPNUserIsEmpty
|
||||||
}
|
}
|
||||||
|
|
||||||
passwordRequired := !isCustom &&
|
if !isCustom && o.Password == "" {
|
||||||
(vpnProvider != providers.Ivpn || !ivpnAccountID.MatchString(o.User))
|
|
||||||
|
|
||||||
if passwordRequired && o.Password == "" {
|
|
||||||
return ErrOpenVPNPasswordIsEmpty
|
return ErrOpenVPNPasswordIsEmpty
|
||||||
}
|
}
|
||||||
|
|
||||||
err = validateOpenVPNConfigFilepath(isCustom, *o.ConfFile)
|
// Validate ConfFile
|
||||||
if err != nil {
|
if isCustom {
|
||||||
return fmt.Errorf("custom configuration file: %w", err)
|
if *o.ConfFile == "" {
|
||||||
|
return fmt.Errorf("%w: no file path specified", ErrOpenVPNConfigFile)
|
||||||
|
}
|
||||||
|
err := helpers.FileExists(*o.ConfFile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%w: %s", ErrOpenVPNConfigFile, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = validateOpenVPNClientCertificate(vpnProvider, *o.ClientCrt)
|
// Check client certificate
|
||||||
if err != nil {
|
switch vpnProvider {
|
||||||
return fmt.Errorf("client certificate: %w", err)
|
case
|
||||||
|
constants.Cyberghost,
|
||||||
|
constants.VPNUnlimited:
|
||||||
|
if *o.ClientCrt == "" {
|
||||||
|
return ErrOpenVPNClientCertMissing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if *o.ClientCrt != "" {
|
||||||
|
_, err = parse.ExtractCert([]byte(*o.ClientCrt))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%w: %s", ErrOpenVPNClientCertNotValid, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = validateOpenVPNClientKey(vpnProvider, *o.ClientKey)
|
// Check client key
|
||||||
if err != nil {
|
switch vpnProvider {
|
||||||
return fmt.Errorf("client key: %w", err)
|
case
|
||||||
|
constants.Cyberghost,
|
||||||
|
constants.VPNUnlimited,
|
||||||
|
constants.Wevpn:
|
||||||
|
if *o.ClientKey == "" {
|
||||||
|
return ErrOpenVPNClientKeyMissing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if *o.ClientKey != "" {
|
||||||
|
_, err = parse.ExtractPrivateKey([]byte(*o.ClientKey))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%w: %s", ErrOpenVPNClientKeyNotValid, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate MSSFix
|
||||||
const maxMSSFix = 10000
|
const maxMSSFix = 10000
|
||||||
if *o.MSSFix > maxMSSFix {
|
if *o.MSSFix > maxMSSFix {
|
||||||
return fmt.Errorf("%w: %d is over the maximum value of %d",
|
return fmt.Errorf("%w: %d is over the maximum value of %d",
|
||||||
@@ -126,6 +152,7 @@ func (o OpenVPN) validate(vpnProvider string) (err error) {
|
|||||||
ErrOpenVPNInterfaceNotValid, o.Interface, regexpInterfaceName)
|
ErrOpenVPNInterfaceNotValid, o.Interface, regexpInterfaceName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate Verbosity
|
||||||
if *o.Verbosity < 0 || *o.Verbosity > 6 {
|
if *o.Verbosity < 0 || *o.Verbosity > 6 {
|
||||||
return fmt.Errorf("%w: %d can only be between 0 and 5",
|
return fmt.Errorf("%w: %d can only be between 0 and 5",
|
||||||
ErrOpenVPNVerbosityIsOutOfBounds, o.Verbosity)
|
ErrOpenVPNVerbosityIsOutOfBounds, o.Verbosity)
|
||||||
@@ -134,74 +161,6 @@ func (o OpenVPN) validate(vpnProvider string) (err error) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateOpenVPNConfigFilepath(isCustom bool,
|
|
||||||
confFile string) (err error) {
|
|
||||||
if !isCustom {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if confFile == "" {
|
|
||||||
return ErrFilepathMissing
|
|
||||||
}
|
|
||||||
|
|
||||||
err = helpers.FileExists(confFile)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
extractor := extract.New()
|
|
||||||
_, _, err = extractor.Data(confFile)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed extracting information from custom configuration file: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func validateOpenVPNClientCertificate(vpnProvider,
|
|
||||||
clientCert string) (err error) {
|
|
||||||
switch vpnProvider {
|
|
||||||
case
|
|
||||||
providers.Cyberghost,
|
|
||||||
providers.VPNUnlimited:
|
|
||||||
if clientCert == "" {
|
|
||||||
return ErrMissingValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if clientCert == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = base64.StdEncoding.DecodeString(clientCert)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func validateOpenVPNClientKey(vpnProvider, clientKey string) (err error) {
|
|
||||||
switch vpnProvider {
|
|
||||||
case
|
|
||||||
providers.Cyberghost,
|
|
||||||
providers.VPNUnlimited,
|
|
||||||
providers.Wevpn:
|
|
||||||
if clientKey == "" {
|
|
||||||
return ErrMissingValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if clientKey == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = base64.StdEncoding.DecodeString(clientKey)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *OpenVPN) copy() (copied OpenVPN) {
|
func (o *OpenVPN) copy() (copied OpenVPN) {
|
||||||
return OpenVPN{
|
return OpenVPN{
|
||||||
Version: o.Version,
|
Version: o.Version,
|
||||||
@@ -216,7 +175,8 @@ func (o *OpenVPN) copy() (copied OpenVPN) {
|
|||||||
IPv6: helpers.CopyBoolPtr(o.IPv6),
|
IPv6: helpers.CopyBoolPtr(o.IPv6),
|
||||||
MSSFix: helpers.CopyUint16Ptr(o.MSSFix),
|
MSSFix: helpers.CopyUint16Ptr(o.MSSFix),
|
||||||
Interface: o.Interface,
|
Interface: o.Interface,
|
||||||
ProcessUser: o.ProcessUser,
|
Root: helpers.CopyBoolPtr(o.Root),
|
||||||
|
ProcUser: o.ProcUser,
|
||||||
Verbosity: helpers.CopyIntPtr(o.Verbosity),
|
Verbosity: helpers.CopyIntPtr(o.Verbosity),
|
||||||
Flags: helpers.CopyStringSlice(o.Flags),
|
Flags: helpers.CopyStringSlice(o.Flags),
|
||||||
}
|
}
|
||||||
@@ -237,8 +197,9 @@ func (o *OpenVPN) mergeWith(other OpenVPN) {
|
|||||||
o.IPv6 = helpers.MergeWithBool(o.IPv6, other.IPv6)
|
o.IPv6 = helpers.MergeWithBool(o.IPv6, other.IPv6)
|
||||||
o.MSSFix = helpers.MergeWithUint16(o.MSSFix, other.MSSFix)
|
o.MSSFix = helpers.MergeWithUint16(o.MSSFix, other.MSSFix)
|
||||||
o.Interface = helpers.MergeWithString(o.Interface, other.Interface)
|
o.Interface = helpers.MergeWithString(o.Interface, other.Interface)
|
||||||
o.ProcessUser = helpers.MergeWithString(o.ProcessUser, other.ProcessUser)
|
o.Root = helpers.MergeWithBool(o.Root, other.Root)
|
||||||
o.Verbosity = helpers.MergeWithIntPtr(o.Verbosity, other.Verbosity)
|
o.ProcUser = helpers.MergeWithString(o.ProcUser, other.ProcUser)
|
||||||
|
o.Verbosity = helpers.MergeWithInt(o.Verbosity, other.Verbosity)
|
||||||
o.Flags = helpers.MergeStringSlices(o.Flags, other.Flags)
|
o.Flags = helpers.MergeStringSlices(o.Flags, other.Flags)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -258,14 +219,15 @@ func (o *OpenVPN) overrideWith(other OpenVPN) {
|
|||||||
o.IPv6 = helpers.OverrideWithBool(o.IPv6, other.IPv6)
|
o.IPv6 = helpers.OverrideWithBool(o.IPv6, other.IPv6)
|
||||||
o.MSSFix = helpers.OverrideWithUint16(o.MSSFix, other.MSSFix)
|
o.MSSFix = helpers.OverrideWithUint16(o.MSSFix, other.MSSFix)
|
||||||
o.Interface = helpers.OverrideWithString(o.Interface, other.Interface)
|
o.Interface = helpers.OverrideWithString(o.Interface, other.Interface)
|
||||||
o.ProcessUser = helpers.OverrideWithString(o.ProcessUser, other.ProcessUser)
|
o.Root = helpers.OverrideWithBool(o.Root, other.Root)
|
||||||
o.Verbosity = helpers.OverrideWithIntPtr(o.Verbosity, other.Verbosity)
|
o.ProcUser = helpers.OverrideWithString(o.ProcUser, other.ProcUser)
|
||||||
|
o.Verbosity = helpers.OverrideWithInt(o.Verbosity, other.Verbosity)
|
||||||
o.Flags = helpers.OverrideWithStringSlice(o.Flags, other.Flags)
|
o.Flags = helpers.OverrideWithStringSlice(o.Flags, other.Flags)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *OpenVPN) setDefaults(vpnProvider string) {
|
func (o *OpenVPN) setDefaults(vpnProvider string) {
|
||||||
o.Version = helpers.DefaultString(o.Version, openvpn.Openvpn25)
|
o.Version = helpers.DefaultString(o.Version, constants.Openvpn25)
|
||||||
if vpnProvider == providers.Mullvad {
|
if vpnProvider == constants.Mullvad {
|
||||||
o.Password = "m"
|
o.Password = "m"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -275,15 +237,16 @@ func (o *OpenVPN) setDefaults(vpnProvider string) {
|
|||||||
o.ClientKey = helpers.DefaultStringPtr(o.ClientKey, "")
|
o.ClientKey = helpers.DefaultStringPtr(o.ClientKey, "")
|
||||||
|
|
||||||
var defaultEncPreset string
|
var defaultEncPreset string
|
||||||
if vpnProvider == providers.PrivateInternetAccess {
|
if vpnProvider == constants.PrivateInternetAccess {
|
||||||
defaultEncPreset = presets.Strong
|
defaultEncPreset = constants.PIAEncryptionPresetStrong
|
||||||
}
|
}
|
||||||
o.PIAEncPreset = helpers.DefaultStringPtr(o.PIAEncPreset, defaultEncPreset)
|
o.PIAEncPreset = helpers.DefaultStringPtr(o.PIAEncPreset, defaultEncPreset)
|
||||||
|
|
||||||
o.IPv6 = helpers.DefaultBool(o.IPv6, false)
|
o.IPv6 = helpers.DefaultBool(o.IPv6, false)
|
||||||
o.MSSFix = helpers.DefaultUint16(o.MSSFix, 0)
|
o.MSSFix = helpers.DefaultUint16(o.MSSFix, 0)
|
||||||
o.Interface = helpers.DefaultString(o.Interface, "tun0")
|
o.Interface = helpers.DefaultString(o.Interface, "tun0")
|
||||||
o.ProcessUser = helpers.DefaultString(o.ProcessUser, "root")
|
o.Root = helpers.DefaultBool(o.Root, true)
|
||||||
|
o.ProcUser = helpers.DefaultString(o.ProcUser, "root")
|
||||||
o.Verbosity = helpers.DefaultInt(o.Verbosity, 1)
|
o.Verbosity = helpers.DefaultInt(o.Verbosity, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -331,7 +294,14 @@ func (o OpenVPN) toLinesNode() (node *gotree.Node) {
|
|||||||
node.Appendf("Network interface: %s", o.Interface)
|
node.Appendf("Network interface: %s", o.Interface)
|
||||||
}
|
}
|
||||||
|
|
||||||
node.Appendf("Run OpenVPN as: %s", o.ProcessUser)
|
processUser := "root"
|
||||||
|
if !*o.Root {
|
||||||
|
processUser = "some non root user" // TODO
|
||||||
|
if o.ProcUser != "" {
|
||||||
|
processUser = o.ProcUser
|
||||||
|
}
|
||||||
|
}
|
||||||
|
node.Appendf("Run OpenVPN as: %s", processUser)
|
||||||
|
|
||||||
node.Appendf("Verbosity level: %d", *o.Verbosity)
|
node.Appendf("Verbosity level: %d", *o.Verbosity)
|
||||||
|
|
||||||
|
|||||||
@@ -1,44 +0,0 @@
|
|||||||
package settings
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Test_ivpnAccountID(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
testCases := []struct {
|
|
||||||
s string
|
|
||||||
match bool
|
|
||||||
}{
|
|
||||||
{},
|
|
||||||
{s: "abc"},
|
|
||||||
{s: "i"},
|
|
||||||
{s: "ivpn"},
|
|
||||||
{s: "ivpn-aaaa"},
|
|
||||||
{s: "ivpn-aaaa-aaaa"},
|
|
||||||
{s: "ivpn-aaaa-aaaa-aaa"},
|
|
||||||
{s: "ivpn-aaaa-aaaa-aaaa", match: true},
|
|
||||||
{s: "ivpn-aaaa-aaaa-aaaaa"},
|
|
||||||
{s: "ivpn-a6B7-fP91-Zh6Y", match: true},
|
|
||||||
{s: "i-aaaa"},
|
|
||||||
{s: "i-aaaa-aaaa"},
|
|
||||||
{s: "i-aaaa-aaaa-aaa"},
|
|
||||||
{s: "i-aaaa-aaaa-aaaa", match: true},
|
|
||||||
{s: "i-aaaa-aaaa-aaaaa"},
|
|
||||||
{s: "i-a6B7-fP91-Zh6Y", match: true},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, testCase := range testCases {
|
|
||||||
testCase := testCase
|
|
||||||
t.Run(testCase.s, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
match := ivpnAccountID.MatchString(testCase.s)
|
|
||||||
|
|
||||||
assert.Equal(t, testCase.match, match)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,8 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
"github.com/qdm12/gluetun/internal/provider/privateinternetaccess/presets"
|
|
||||||
"github.com/qdm12/gotree"
|
"github.com/qdm12/gotree"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -34,17 +33,15 @@ func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
|
|||||||
if confFile := *o.ConfFile; confFile != "" {
|
if confFile := *o.ConfFile; confFile != "" {
|
||||||
err := helpers.FileExists(confFile)
|
err := helpers.FileExists(confFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("configuration file: %w", err)
|
return fmt.Errorf("%w: %s", ErrOpenVPNConfigFile, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate TCP
|
// Validate TCP
|
||||||
if *o.TCP && helpers.IsOneOf(vpnProvider,
|
if *o.TCP && helpers.IsOneOf(vpnProvider,
|
||||||
providers.Ipvanish,
|
constants.Perfectprivacy,
|
||||||
providers.Perfectprivacy,
|
constants.Privado,
|
||||||
providers.Privado,
|
constants.Vyprvpn,
|
||||||
providers.VPNUnlimited,
|
|
||||||
providers.Vyprvpn,
|
|
||||||
) {
|
) {
|
||||||
return fmt.Errorf("%w: for VPN service provider %s",
|
return fmt.Errorf("%w: for VPN service provider %s",
|
||||||
ErrOpenVPNTCPNotSupported, vpnProvider)
|
ErrOpenVPNTCPNotSupported, vpnProvider)
|
||||||
@@ -54,38 +51,33 @@ func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
|
|||||||
if *o.CustomPort != 0 {
|
if *o.CustomPort != 0 {
|
||||||
switch vpnProvider {
|
switch vpnProvider {
|
||||||
// no restriction on port
|
// no restriction on port
|
||||||
case providers.Cyberghost, providers.HideMyAss,
|
case constants.Cyberghost, constants.HideMyAss,
|
||||||
providers.Privatevpn, providers.Torguard:
|
constants.PrivateInternetAccess, constants.Privatevpn,
|
||||||
|
constants.Protonvpn, constants.Torguard:
|
||||||
// no custom port allowed
|
// no custom port allowed
|
||||||
case providers.Expressvpn, providers.Fastestvpn,
|
case constants.Expressvpn, constants.Fastestvpn,
|
||||||
providers.Ipvanish, providers.Nordvpn,
|
constants.Ipvanish, constants.Nordvpn,
|
||||||
providers.Privado, providers.Purevpn,
|
constants.Privado, constants.Purevpn,
|
||||||
providers.Surfshark, providers.VPNUnlimited,
|
constants.Surfshark, constants.VPNUnlimited,
|
||||||
providers.Vyprvpn:
|
constants.Vyprvpn:
|
||||||
return fmt.Errorf("%w: for VPN service provider %s",
|
return fmt.Errorf("%w: for VPN service provider %s",
|
||||||
ErrOpenVPNCustomPortNotAllowed, vpnProvider)
|
ErrOpenVPNCustomPortNotAllowed, vpnProvider)
|
||||||
default:
|
default:
|
||||||
var allowedTCP, allowedUDP []uint16
|
var allowedTCP, allowedUDP []uint16
|
||||||
switch vpnProvider {
|
switch vpnProvider {
|
||||||
case providers.Ivpn:
|
case constants.Ivpn:
|
||||||
allowedTCP = []uint16{80, 443, 1143}
|
allowedTCP = []uint16{80, 443, 1143}
|
||||||
allowedUDP = []uint16{53, 1194, 2049, 2050}
|
allowedUDP = []uint16{53, 1194, 2049, 2050}
|
||||||
case providers.Mullvad:
|
case constants.Mullvad:
|
||||||
allowedTCP = []uint16{80, 443, 1401}
|
allowedTCP = []uint16{80, 443, 1401}
|
||||||
allowedUDP = []uint16{53, 1194, 1195, 1196, 1197, 1300, 1301, 1302, 1303, 1400}
|
allowedUDP = []uint16{53, 1194, 1195, 1196, 1197, 1300, 1301, 1302, 1303, 1400}
|
||||||
case providers.Perfectprivacy:
|
case constants.Perfectprivacy:
|
||||||
allowedTCP = []uint16{44, 443, 4433}
|
allowedTCP = []uint16{44, 443, 4433}
|
||||||
allowedUDP = []uint16{44, 443, 4433}
|
allowedUDP = []uint16{44, 443, 4433}
|
||||||
case providers.PrivateInternetAccess:
|
case constants.Wevpn:
|
||||||
allowedTCP = []uint16{80, 110, 443}
|
|
||||||
allowedUDP = []uint16{53, 1194, 1197, 1198, 8080, 9201}
|
|
||||||
case providers.Protonvpn:
|
|
||||||
allowedTCP = []uint16{443, 5995, 8443}
|
|
||||||
allowedUDP = []uint16{80, 443, 1194, 4569, 5060}
|
|
||||||
case providers.Wevpn:
|
|
||||||
allowedTCP = []uint16{53, 1195, 1199, 2018}
|
allowedTCP = []uint16{53, 1195, 1199, 2018}
|
||||||
allowedUDP = []uint16{80, 1194, 1198}
|
allowedUDP = []uint16{80, 1194, 1198}
|
||||||
case providers.Windscribe:
|
case constants.Windscribe:
|
||||||
allowedTCP = []uint16{21, 22, 80, 123, 143, 443, 587, 1194, 3306, 8080, 54783}
|
allowedTCP = []uint16{21, 22, 80, 123, 143, 443, 587, 1194, 3306, 8080, 54783}
|
||||||
allowedUDP = []uint16{53, 80, 123, 443, 1194, 54783}
|
allowedUDP = []uint16{53, 80, 123, 443, 1194, 54783}
|
||||||
}
|
}
|
||||||
@@ -103,11 +95,11 @@ func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Validate EncPreset
|
// Validate EncPreset
|
||||||
if vpnProvider == providers.PrivateInternetAccess {
|
if vpnProvider == constants.PrivateInternetAccess {
|
||||||
validEncryptionPresets := []string{
|
validEncryptionPresets := []string{
|
||||||
presets.None,
|
constants.PIAEncryptionPresetNone,
|
||||||
presets.Normal,
|
constants.PIAEncryptionPresetNormal,
|
||||||
presets.Strong,
|
constants.PIAEncryptionPresetStrong,
|
||||||
}
|
}
|
||||||
if !helpers.IsOneOf(*o.PIAEncPreset, validEncryptionPresets...) {
|
if !helpers.IsOneOf(*o.PIAEncPreset, validEncryptionPresets...) {
|
||||||
return fmt.Errorf("%w: %s; valid presets are %s",
|
return fmt.Errorf("%w: %s; valid presets are %s",
|
||||||
@@ -148,8 +140,8 @@ func (o *OpenVPNSelection) setDefaults(vpnProvider string) {
|
|||||||
o.CustomPort = helpers.DefaultUint16(o.CustomPort, 0)
|
o.CustomPort = helpers.DefaultUint16(o.CustomPort, 0)
|
||||||
|
|
||||||
var defaultEncPreset string
|
var defaultEncPreset string
|
||||||
if vpnProvider == providers.PrivateInternetAccess {
|
if vpnProvider == constants.PrivateInternetAccess {
|
||||||
defaultEncPreset = presets.Strong
|
defaultEncPreset = constants.PIAEncryptionPresetStrong
|
||||||
}
|
}
|
||||||
o.PIAEncPreset = helpers.DefaultStringPtr(o.PIAEncPreset, defaultEncPreset)
|
o.PIAEncPreset = helpers.DefaultStringPtr(o.PIAEncPreset, defaultEncPreset)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
"github.com/qdm12/gotree"
|
"github.com/qdm12/gotree"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ func (p PortForwarding) validate(vpnProvider string) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Validate Enabled
|
// Validate Enabled
|
||||||
validProviders := []string{providers.PrivateInternetAccess}
|
validProviders := []string{constants.PrivateInternetAccess}
|
||||||
if !helpers.IsOneOf(vpnProvider, validProviders...) {
|
if !helpers.IsOneOf(vpnProvider, validProviders...) {
|
||||||
return fmt.Errorf("%w: for provider %s, it is only available for %s",
|
return fmt.Errorf("%w: for provider %s, it is only available for %s",
|
||||||
ErrPortForwardingEnabled, vpnProvider, strings.Join(validProviders, ", "))
|
ErrPortForwardingEnabled, vpnProvider, strings.Join(validProviders, ", "))
|
||||||
@@ -38,7 +38,7 @@ func (p PortForwarding) validate(vpnProvider string) (err error) {
|
|||||||
if *p.Filepath != "" { // optional
|
if *p.Filepath != "" { // optional
|
||||||
_, err := filepath.Abs(*p.Filepath)
|
_, err := filepath.Abs(*p.Filepath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("filepath is not valid: %w", err)
|
return fmt.Errorf("%w: %s", ErrPortForwardingFilepathNotValid, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
"github.com/qdm12/gluetun/internal/constants/vpn"
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
"github.com/qdm12/gotree"
|
"github.com/qdm12/gotree"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -22,18 +22,18 @@ type Provider struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO v4 remove pointer for receiver (because of Surfshark).
|
// TODO v4 remove pointer for receiver (because of Surfshark).
|
||||||
func (p *Provider) validate(vpnType string, storage Storage) (err error) {
|
func (p *Provider) validate(vpnType string, allServers models.AllServers) (err error) {
|
||||||
// Validate Name
|
// Validate Name
|
||||||
var validNames []string
|
var validNames []string
|
||||||
if vpnType == vpn.OpenVPN {
|
if vpnType == constants.OpenVPN {
|
||||||
validNames = providers.AllWithCustom()
|
validNames = constants.AllProviders()
|
||||||
validNames = append(validNames, "pia") // Retro-compatibility
|
validNames = append(validNames, "pia") // Retro-compatibility
|
||||||
} else { // Wireguard
|
} else { // Wireguard
|
||||||
validNames = []string{
|
validNames = []string{
|
||||||
providers.Custom,
|
constants.Custom,
|
||||||
providers.Ivpn,
|
constants.Ivpn,
|
||||||
providers.Mullvad,
|
constants.Mullvad,
|
||||||
providers.Windscribe,
|
constants.Windscribe,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !helpers.IsOneOf(*p.Name, validNames...) {
|
if !helpers.IsOneOf(*p.Name, validNames...) {
|
||||||
@@ -41,14 +41,14 @@ func (p *Provider) validate(vpnType string, storage Storage) (err error) {
|
|||||||
ErrVPNProviderNameNotValid, *p.Name, helpers.ChoicesOrString(validNames))
|
ErrVPNProviderNameNotValid, *p.Name, helpers.ChoicesOrString(validNames))
|
||||||
}
|
}
|
||||||
|
|
||||||
err = p.ServerSelection.validate(*p.Name, storage)
|
err = p.ServerSelection.validate(*p.Name, allServers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("server selection: %w", err)
|
return fmt.Errorf("server selection settings validation failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = p.PortForwarding.validate(*p.Name)
|
err = p.PortForwarding.validate(*p.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("port forwarding: %w", err)
|
return fmt.Errorf("port forwarding settings validation failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -75,7 +75,7 @@ func (p *Provider) overrideWith(other Provider) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *Provider) setDefaults() {
|
func (p *Provider) setDefaults() {
|
||||||
p.Name = helpers.DefaultStringPtr(p.Name, providers.PrivateInternetAccess)
|
p.Name = helpers.DefaultStringPtr(p.Name, constants.PrivateInternetAccess)
|
||||||
p.ServerSelection.setDefaults(*p.Name)
|
p.ServerSelection.setDefaults(*p.Name)
|
||||||
p.PortForwarding.setDefaults()
|
p.PortForwarding.setDefaults()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ func (p PublicIP) validate() (err error) {
|
|||||||
if *p.IPFilepath != "" { // optional
|
if *p.IPFilepath != "" { // optional
|
||||||
_, err := filepath.Abs(*p.IPFilepath)
|
_, err := filepath.Abs(*p.IPFilepath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("filepath is not valid: %w", err)
|
return fmt.Errorf("%w: %s", ErrPublicIPFilepathNotValid, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,7 @@ package settings
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||||
"github.com/qdm12/gotree"
|
"github.com/qdm12/gotree"
|
||||||
@@ -12,30 +10,22 @@ import (
|
|||||||
|
|
||||||
// ControlServer contains settings to customize the control server operation.
|
// ControlServer contains settings to customize the control server operation.
|
||||||
type ControlServer struct {
|
type ControlServer struct {
|
||||||
// Address is the listening address to use.
|
// Port is the listening port to use.
|
||||||
|
// It can be set to 0 to bind to a random port.
|
||||||
// It cannot be nil in the internal state.
|
// It cannot be nil in the internal state.
|
||||||
Address *string
|
// TODO change to address
|
||||||
|
Port *uint16
|
||||||
// Log can be true or false to enable logging on requests.
|
// Log can be true or false to enable logging on requests.
|
||||||
// It cannot be nil in the internal state.
|
// It cannot be nil in the internal state.
|
||||||
Log *bool
|
Log *bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c ControlServer) validate() (err error) {
|
func (c ControlServer) validate() (err error) {
|
||||||
_, portStr, err := net.SplitHostPort(*c.Address)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("listening address is not valid: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
port, err := strconv.Atoi(portStr)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("listening port it not valid: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
uid := os.Getuid()
|
uid := os.Getuid()
|
||||||
const maxPrivilegedPort = 1023
|
const maxPrivilegedPort uint16 = 1023
|
||||||
if uid != 0 && port != 0 && port <= maxPrivilegedPort {
|
if uid != 0 && *c.Port <= maxPrivilegedPort {
|
||||||
return fmt.Errorf("%w: %d when running with user ID %d",
|
return fmt.Errorf("%w: %d when running with user ID %d",
|
||||||
ErrControlServerPrivilegedPort, port, uid)
|
ErrControlServerPrivilegedPort, *c.Port, uid)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -43,15 +33,15 @@ func (c ControlServer) validate() (err error) {
|
|||||||
|
|
||||||
func (c *ControlServer) copy() (copied ControlServer) {
|
func (c *ControlServer) copy() (copied ControlServer) {
|
||||||
return ControlServer{
|
return ControlServer{
|
||||||
Address: helpers.CopyStringPtr(c.Address),
|
Port: helpers.CopyUint16Ptr(c.Port),
|
||||||
Log: helpers.CopyBoolPtr(c.Log),
|
Log: helpers.CopyBoolPtr(c.Log),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// mergeWith merges the other settings into any
|
// mergeWith merges the other settings into any
|
||||||
// unset field of the receiver settings object.
|
// unset field of the receiver settings object.
|
||||||
func (c *ControlServer) mergeWith(other ControlServer) {
|
func (c *ControlServer) mergeWith(other ControlServer) {
|
||||||
c.Address = helpers.MergeWithStringPtr(c.Address, other.Address)
|
c.Port = helpers.MergeWithUint16(c.Port, other.Port)
|
||||||
c.Log = helpers.MergeWithBool(c.Log, other.Log)
|
c.Log = helpers.MergeWithBool(c.Log, other.Log)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,12 +49,13 @@ func (c *ControlServer) mergeWith(other ControlServer) {
|
|||||||
// settings object with any field set in the other
|
// settings object with any field set in the other
|
||||||
// settings.
|
// settings.
|
||||||
func (c *ControlServer) overrideWith(other ControlServer) {
|
func (c *ControlServer) overrideWith(other ControlServer) {
|
||||||
c.Address = helpers.OverrideWithStringPtr(c.Address, other.Address)
|
c.Port = helpers.MergeWithUint16(c.Port, other.Port)
|
||||||
c.Log = helpers.OverrideWithBool(c.Log, other.Log)
|
c.Log = helpers.MergeWithBool(c.Log, other.Log)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ControlServer) setDefaults() {
|
func (c *ControlServer) setDefaults() {
|
||||||
c.Address = helpers.DefaultStringPtr(c.Address, ":8000")
|
const defaultPort = 8000
|
||||||
|
c.Port = helpers.DefaultUint16(c.Port, defaultPort)
|
||||||
c.Log = helpers.DefaultBool(c.Log, true)
|
c.Log = helpers.DefaultBool(c.Log, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,7 +65,7 @@ func (c ControlServer) String() string {
|
|||||||
|
|
||||||
func (c ControlServer) toLinesNode() (node *gotree.Node) {
|
func (c ControlServer) toLinesNode() (node *gotree.Node) {
|
||||||
node = gotree.New("Control server settings:")
|
node = gotree.New("Control server settings:")
|
||||||
node.Appendf("Listening address: %s", *c.Address)
|
node.Appendf("Listening port: %d", *c.Port)
|
||||||
node.Appendf("Logging: %s", helpers.BoolPtrToYesNo(c.Log))
|
node.Appendf("Logging: %s", helpers.BoolPtrToYesNo(c.Log))
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,12 @@
|
|||||||
package settings
|
package settings
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings/validation"
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
|
||||||
"github.com/qdm12/gluetun/internal/constants/vpn"
|
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
"github.com/qdm12/gotree"
|
"github.com/qdm12/gotree"
|
||||||
)
|
)
|
||||||
@@ -39,16 +36,16 @@ type ServerSelection struct { //nolint:maligned
|
|||||||
Numbers []uint16
|
Numbers []uint16
|
||||||
// Hostnames is the list of hostnames to filter VPN servers with.
|
// Hostnames is the list of hostnames to filter VPN servers with.
|
||||||
Hostnames []string
|
Hostnames []string
|
||||||
// OwnedOnly is true if VPN provider servers that are not owned
|
// OwnedOnly is true if only VPN provider owned servers
|
||||||
// should be filtered. This is used with Mullvad.
|
// should be filtered. This is used with Mullvad.
|
||||||
OwnedOnly *bool
|
OwnedOnly *bool
|
||||||
// FreeOnly is true if VPN servers that are not free should
|
// FreeOnly is true if only free VPN servers
|
||||||
// be filtered. This is used with ProtonVPN and VPN Unlimited.
|
// should be filtered. This is used with ProtonVPN.
|
||||||
FreeOnly *bool
|
FreeOnly *bool
|
||||||
// StreamOnly is true if VPN servers not for streaming should
|
// FreeOnly is true if only free VPN servers
|
||||||
// be filtered. This is used with VPNUnlimited.
|
// should be filtered. This is used with ProtonVPN.
|
||||||
StreamOnly *bool
|
StreamOnly *bool
|
||||||
// MultiHopOnly is true if VPN servers that are not multihop
|
// MultiHopOnly is true if only multihop VPN servers
|
||||||
// should be filtered. This is used with Surfshark.
|
// should be filtered. This is used with Surfshark.
|
||||||
MultiHopOnly *bool
|
MultiHopOnly *bool
|
||||||
|
|
||||||
@@ -60,122 +57,190 @@ type ServerSelection struct { //nolint:maligned
|
|||||||
Wireguard WireguardSelection
|
Wireguard WireguardSelection
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
|
||||||
ErrOwnedOnlyNotSupported = errors.New("owned only filter is not supported")
|
|
||||||
ErrFreeOnlyNotSupported = errors.New("free only filter is not supported")
|
|
||||||
ErrStreamOnlyNotSupported = errors.New("stream only filter is not supported")
|
|
||||||
ErrMultiHopOnlyNotSupported = errors.New("multi hop only filter is not supported")
|
|
||||||
)
|
|
||||||
|
|
||||||
func (ss *ServerSelection) validate(vpnServiceProvider string,
|
func (ss *ServerSelection) validate(vpnServiceProvider string,
|
||||||
storage Storage) (err error) {
|
allServers models.AllServers) (err error) {
|
||||||
switch ss.VPN {
|
switch ss.VPN {
|
||||||
case vpn.OpenVPN, vpn.Wireguard:
|
case constants.OpenVPN, constants.Wireguard:
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("%w: %s", ErrVPNTypeNotValid, ss.VPN)
|
return fmt.Errorf("%w: %s", ErrVPNTypeNotValid, ss.VPN)
|
||||||
}
|
}
|
||||||
|
|
||||||
filterChoices, err := getLocationFilterChoices(vpnServiceProvider, ss, storage)
|
var countryChoices, regionChoices, cityChoices,
|
||||||
if err != nil {
|
ispChoices, nameChoices, hostnameChoices []string
|
||||||
return err // already wrapped error
|
switch vpnServiceProvider {
|
||||||
}
|
case constants.Custom:
|
||||||
|
case constants.Cyberghost:
|
||||||
err = validateServerFilters(*ss, filterChoices)
|
servers := allServers.GetCyberghost()
|
||||||
if err != nil {
|
countryChoices = constants.CyberghostCountryChoices(servers)
|
||||||
if errors.Is(err, helpers.ErrNoChoice) {
|
hostnameChoices = constants.CyberghostHostnameChoices(servers)
|
||||||
return fmt.Errorf("for VPN service provider %s: %w", vpnServiceProvider, err)
|
case constants.Expressvpn:
|
||||||
|
servers := allServers.GetExpressvpn()
|
||||||
|
countryChoices = constants.ExpressvpnCountriesChoices(servers)
|
||||||
|
cityChoices = constants.ExpressvpnCityChoices(servers)
|
||||||
|
hostnameChoices = constants.ExpressvpnHostnameChoices(servers)
|
||||||
|
case constants.Fastestvpn:
|
||||||
|
servers := allServers.GetFastestvpn()
|
||||||
|
countryChoices = constants.FastestvpnCountriesChoices(servers)
|
||||||
|
hostnameChoices = constants.FastestvpnHostnameChoices(servers)
|
||||||
|
case constants.HideMyAss:
|
||||||
|
servers := allServers.GetHideMyAss()
|
||||||
|
countryChoices = constants.HideMyAssCountryChoices(servers)
|
||||||
|
regionChoices = constants.HideMyAssRegionChoices(servers)
|
||||||
|
cityChoices = constants.HideMyAssCityChoices(servers)
|
||||||
|
hostnameChoices = constants.HideMyAssHostnameChoices(servers)
|
||||||
|
case constants.Ipvanish:
|
||||||
|
servers := allServers.GetIpvanish()
|
||||||
|
countryChoices = constants.IpvanishCountryChoices(servers)
|
||||||
|
cityChoices = constants.IpvanishCityChoices(servers)
|
||||||
|
hostnameChoices = constants.IpvanishHostnameChoices(servers)
|
||||||
|
case constants.Ivpn:
|
||||||
|
servers := allServers.GetIvpn()
|
||||||
|
countryChoices = constants.IvpnCountryChoices(servers)
|
||||||
|
cityChoices = constants.IvpnCityChoices(servers)
|
||||||
|
ispChoices = constants.IvpnISPChoices(servers)
|
||||||
|
hostnameChoices = constants.IvpnHostnameChoices(servers)
|
||||||
|
case constants.Mullvad:
|
||||||
|
servers := allServers.GetMullvad()
|
||||||
|
countryChoices = constants.MullvadCountryChoices(servers)
|
||||||
|
cityChoices = constants.MullvadCityChoices(servers)
|
||||||
|
ispChoices = constants.MullvadISPChoices(servers)
|
||||||
|
hostnameChoices = constants.MullvadHostnameChoices(servers)
|
||||||
|
case constants.Nordvpn:
|
||||||
|
servers := allServers.GetNordvpn()
|
||||||
|
regionChoices = constants.NordvpnRegionChoices(servers)
|
||||||
|
hostnameChoices = constants.NordvpnHostnameChoices(servers)
|
||||||
|
case constants.Perfectprivacy:
|
||||||
|
servers := allServers.GetPerfectprivacy()
|
||||||
|
cityChoices = constants.PerfectprivacyCityChoices(servers)
|
||||||
|
case constants.Privado:
|
||||||
|
servers := allServers.GetPrivado()
|
||||||
|
countryChoices = constants.PrivadoCountryChoices(servers)
|
||||||
|
regionChoices = constants.PrivadoRegionChoices(servers)
|
||||||
|
cityChoices = constants.PrivadoCityChoices(servers)
|
||||||
|
hostnameChoices = constants.PrivadoHostnameChoices(servers)
|
||||||
|
case constants.PrivateInternetAccess:
|
||||||
|
servers := allServers.GetPia()
|
||||||
|
regionChoices = constants.PIAGeoChoices(servers)
|
||||||
|
hostnameChoices = constants.PIAHostnameChoices(servers)
|
||||||
|
nameChoices = constants.PIANameChoices(servers)
|
||||||
|
case constants.Privatevpn:
|
||||||
|
servers := allServers.GetPrivatevpn()
|
||||||
|
countryChoices = constants.PrivatevpnCountryChoices(servers)
|
||||||
|
cityChoices = constants.PrivatevpnCityChoices(servers)
|
||||||
|
hostnameChoices = constants.PrivatevpnHostnameChoices(servers)
|
||||||
|
case constants.Protonvpn:
|
||||||
|
servers := allServers.GetProtonvpn()
|
||||||
|
countryChoices = constants.ProtonvpnCountryChoices(servers)
|
||||||
|
regionChoices = constants.ProtonvpnRegionChoices(servers)
|
||||||
|
cityChoices = constants.ProtonvpnCityChoices(servers)
|
||||||
|
nameChoices = constants.ProtonvpnNameChoices(servers)
|
||||||
|
hostnameChoices = constants.ProtonvpnHostnameChoices(servers)
|
||||||
|
case constants.Purevpn:
|
||||||
|
servers := allServers.GetPurevpn()
|
||||||
|
countryChoices = constants.PurevpnCountryChoices(servers)
|
||||||
|
regionChoices = constants.PurevpnRegionChoices(servers)
|
||||||
|
cityChoices = constants.PurevpnCityChoices(servers)
|
||||||
|
hostnameChoices = constants.PurevpnHostnameChoices(servers)
|
||||||
|
case constants.Surfshark:
|
||||||
|
servers := allServers.GetSurfshark()
|
||||||
|
countryChoices = constants.SurfsharkCountryChoices(servers)
|
||||||
|
cityChoices = constants.SurfsharkCityChoices(servers)
|
||||||
|
hostnameChoices = constants.SurfsharkHostnameChoices(servers)
|
||||||
|
regionChoices = constants.SurfsharkRegionChoices(servers)
|
||||||
|
// TODO v4 remove
|
||||||
|
regionChoices = append(regionChoices, constants.SurfsharkRetroLocChoices(servers)...)
|
||||||
|
if err := helpers.AreAllOneOf(ss.Regions, regionChoices); err != nil {
|
||||||
|
return fmt.Errorf("%w: %s", ErrRegionNotValid, err)
|
||||||
}
|
}
|
||||||
|
// Retro compatibility
|
||||||
|
// TODO remove in v4
|
||||||
|
*ss = surfsharkRetroRegion(*ss)
|
||||||
|
case constants.Torguard:
|
||||||
|
servers := allServers.GetTorguard()
|
||||||
|
countryChoices = constants.TorguardCountryChoices(servers)
|
||||||
|
cityChoices = constants.TorguardCityChoices(servers)
|
||||||
|
hostnameChoices = constants.TorguardHostnameChoices(servers)
|
||||||
|
case constants.VPNUnlimited:
|
||||||
|
servers := allServers.GetVPNUnlimited()
|
||||||
|
countryChoices = constants.VPNUnlimitedCountryChoices(servers)
|
||||||
|
cityChoices = constants.VPNUnlimitedCityChoices(servers)
|
||||||
|
hostnameChoices = constants.VPNUnlimitedHostnameChoices(servers)
|
||||||
|
case constants.Vyprvpn:
|
||||||
|
servers := allServers.GetVyprvpn()
|
||||||
|
regionChoices = constants.VyprvpnRegionChoices(servers)
|
||||||
|
case constants.Wevpn:
|
||||||
|
servers := allServers.GetWevpn()
|
||||||
|
cityChoices = constants.WevpnCityChoices(servers)
|
||||||
|
hostnameChoices = constants.WevpnHostnameChoices(servers)
|
||||||
|
case constants.Windscribe:
|
||||||
|
servers := allServers.GetWindscribe()
|
||||||
|
regionChoices = constants.WindscribeRegionChoices(servers)
|
||||||
|
cityChoices = constants.WindscribeCityChoices(servers)
|
||||||
|
hostnameChoices = constants.WindscribeHostnameChoices(servers)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("%w: %s", ErrVPNProviderNameNotValid, vpnServiceProvider)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = validateServerFilters(*ss, countryChoices, regionChoices, cityChoices,
|
||||||
|
ispChoices, nameChoices, hostnameChoices)
|
||||||
|
if err != nil {
|
||||||
return err // already wrapped error
|
return err // already wrapped error
|
||||||
}
|
}
|
||||||
|
|
||||||
if *ss.OwnedOnly &&
|
if ss.VPN == constants.OpenVPN {
|
||||||
vpnServiceProvider != providers.Mullvad {
|
|
||||||
return fmt.Errorf("%w: for VPN service provider %s",
|
|
||||||
ErrOwnedOnlyNotSupported, vpnServiceProvider)
|
|
||||||
}
|
|
||||||
|
|
||||||
if *ss.FreeOnly &&
|
|
||||||
!helpers.IsOneOf(vpnServiceProvider,
|
|
||||||
providers.Protonvpn,
|
|
||||||
providers.VPNUnlimited,
|
|
||||||
) {
|
|
||||||
return fmt.Errorf("%w: for VPN service provider %s",
|
|
||||||
ErrFreeOnlyNotSupported, vpnServiceProvider)
|
|
||||||
}
|
|
||||||
|
|
||||||
if *ss.StreamOnly &&
|
|
||||||
!helpers.IsOneOf(vpnServiceProvider,
|
|
||||||
providers.Protonvpn,
|
|
||||||
providers.VPNUnlimited,
|
|
||||||
) {
|
|
||||||
return fmt.Errorf("%w: for VPN service provider %s",
|
|
||||||
ErrStreamOnlyNotSupported, vpnServiceProvider)
|
|
||||||
}
|
|
||||||
|
|
||||||
if *ss.MultiHopOnly &&
|
|
||||||
vpnServiceProvider != providers.Surfshark {
|
|
||||||
return fmt.Errorf("%w: for VPN service provider %s",
|
|
||||||
ErrMultiHopOnlyNotSupported, vpnServiceProvider)
|
|
||||||
}
|
|
||||||
|
|
||||||
if ss.VPN == vpn.OpenVPN {
|
|
||||||
err = ss.OpenVPN.validate(vpnServiceProvider)
|
err = ss.OpenVPN.validate(vpnServiceProvider)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("OpenVPN server selection settings: %w", err)
|
return fmt.Errorf("OpenVPN server selection settings validation failed: %w", err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
err = ss.Wireguard.validate(vpnServiceProvider)
|
err = ss.Wireguard.validate(vpnServiceProvider)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Wireguard server selection settings: %w", err)
|
return fmt.Errorf("Wireguard server selection settings validation failed: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getLocationFilterChoices(vpnServiceProvider string,
|
|
||||||
ss *ServerSelection, storage Storage) (filterChoices models.FilterChoices,
|
|
||||||
err error) {
|
|
||||||
filterChoices = storage.GetFilterChoices(vpnServiceProvider)
|
|
||||||
|
|
||||||
if vpnServiceProvider == providers.Surfshark {
|
|
||||||
// // Retro compatibility
|
|
||||||
// TODO v4 remove
|
|
||||||
filterChoices.Regions = append(filterChoices.Regions, validation.SurfsharkRetroLocChoices()...)
|
|
||||||
if err := helpers.AreAllOneOf(ss.Regions, filterChoices.Regions); err != nil {
|
|
||||||
return models.FilterChoices{}, fmt.Errorf("%w: %s", ErrRegionNotValid, err)
|
|
||||||
}
|
|
||||||
*ss = surfsharkRetroRegion(*ss)
|
|
||||||
}
|
|
||||||
|
|
||||||
return filterChoices, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// validateServerFilters validates filters against the choices given as arguments.
|
// validateServerFilters validates filters against the choices given as arguments.
|
||||||
// Set an argument to nil to pass the check for a particular filter.
|
// Set an argument to nil to pass the check for a particular filter.
|
||||||
func validateServerFilters(settings ServerSelection, filterChoices models.FilterChoices) (err error) {
|
func validateServerFilters(settings ServerSelection,
|
||||||
if err := helpers.AreAllOneOf(settings.Countries, filterChoices.Countries); err != nil {
|
countryChoices, regionChoices, cityChoices, ispChoices,
|
||||||
return fmt.Errorf("%w: %s", ErrCountryNotValid, err)
|
nameChoices, hostnameChoices []string) (err error) {
|
||||||
|
if countryChoices != nil {
|
||||||
|
if err := helpers.AreAllOneOf(settings.Countries, countryChoices); err != nil {
|
||||||
|
return fmt.Errorf("%w: %s", ErrCountryNotValid, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := helpers.AreAllOneOf(settings.Regions, filterChoices.Regions); err != nil {
|
if regionChoices != nil {
|
||||||
return fmt.Errorf("%w: %s", ErrRegionNotValid, err)
|
if err := helpers.AreAllOneOf(settings.Regions, regionChoices); err != nil {
|
||||||
|
return fmt.Errorf("%w: %s", ErrRegionNotValid, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := helpers.AreAllOneOf(settings.Cities, filterChoices.Cities); err != nil {
|
if cityChoices != nil {
|
||||||
return fmt.Errorf("%w: %s", ErrCityNotValid, err)
|
if err := helpers.AreAllOneOf(settings.Cities, cityChoices); err != nil {
|
||||||
|
return fmt.Errorf("%w: %s", ErrCityNotValid, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := helpers.AreAllOneOf(settings.ISPs, filterChoices.ISPs); err != nil {
|
if ispChoices != nil {
|
||||||
return fmt.Errorf("%w: %s", ErrISPNotValid, err)
|
if err := helpers.AreAllOneOf(settings.ISPs, ispChoices); err != nil {
|
||||||
|
return fmt.Errorf("%w: %s", ErrISPNotValid, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := helpers.AreAllOneOf(settings.Hostnames, filterChoices.Hostnames); err != nil {
|
if hostnameChoices != nil {
|
||||||
return fmt.Errorf("%w: %s", ErrHostnameNotValid, err)
|
if err := helpers.AreAllOneOf(settings.Hostnames, hostnameChoices); err != nil {
|
||||||
|
return fmt.Errorf("%w: %s", ErrHostnameNotValid, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := helpers.AreAllOneOf(settings.Names, filterChoices.Names); err != nil {
|
if nameChoices != nil {
|
||||||
return fmt.Errorf("%w: %s", ErrNameNotValid, err)
|
if err := helpers.AreAllOneOf(settings.Names, nameChoices); err != nil {
|
||||||
|
return fmt.Errorf("%w: %s", ErrNameNotValid, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -239,7 +304,7 @@ func (ss *ServerSelection) overrideWith(other ServerSelection) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ss *ServerSelection) setDefaults(vpnProvider string) {
|
func (ss *ServerSelection) setDefaults(vpnProvider string) {
|
||||||
ss.VPN = helpers.DefaultString(ss.VPN, vpn.OpenVPN)
|
ss.VPN = helpers.DefaultString(ss.VPN, constants.OpenVPN)
|
||||||
ss.TargetIP = helpers.DefaultIP(ss.TargetIP, net.IP{})
|
ss.TargetIP = helpers.DefaultIP(ss.TargetIP, net.IP{})
|
||||||
ss.OwnedOnly = helpers.DefaultBool(ss.OwnedOnly, false)
|
ss.OwnedOnly = helpers.DefaultBool(ss.OwnedOnly, false)
|
||||||
ss.FreeOnly = helpers.DefaultBool(ss.FreeOnly, false)
|
ss.FreeOnly = helpers.DefaultBool(ss.FreeOnly, false)
|
||||||
@@ -307,7 +372,7 @@ func (ss ServerSelection) toLinesNode() (node *gotree.Node) {
|
|||||||
node.Appendf("Multi-hop only servers: yes")
|
node.Appendf("Multi-hop only servers: yes")
|
||||||
}
|
}
|
||||||
|
|
||||||
if ss.VPN == vpn.OpenVPN {
|
if ss.VPN == constants.OpenVPN {
|
||||||
node.AppendNode(ss.OpenVPN.toLinesNode())
|
node.AppendNode(ss.OpenVPN.toLinesNode())
|
||||||
} else {
|
} else {
|
||||||
node.AppendNode(ss.Wireguard.toLinesNode())
|
node.AppendNode(ss.Wireguard.toLinesNode())
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
"github.com/qdm12/gluetun/internal/pprof"
|
|
||||||
"github.com/qdm12/gotree"
|
"github.com/qdm12/gotree"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -21,17 +20,12 @@ type Settings struct {
|
|||||||
Updater Updater
|
Updater Updater
|
||||||
Version Version
|
Version Version
|
||||||
VPN VPN
|
VPN VPN
|
||||||
Pprof pprof.Settings
|
|
||||||
}
|
|
||||||
|
|
||||||
type Storage interface {
|
|
||||||
GetFilterChoices(provider string) models.FilterChoices
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate validates all the settings and returns an error
|
// Validate validates all the settings and returns an error
|
||||||
// if one of them is not valid.
|
// if one of them is not valid.
|
||||||
// TODO v4 remove pointer for receiver (because of Surfshark).
|
// TODO v4 remove pointer for receiver (because of Surfshark).
|
||||||
func (s *Settings) Validate(storage Storage) (err error) {
|
func (s *Settings) Validate(allServers models.AllServers) (err error) {
|
||||||
nameToValidation := map[string]func() error{
|
nameToValidation := map[string]func() error{
|
||||||
"control server": s.ControlServer.validate,
|
"control server": s.ControlServer.validate,
|
||||||
"dns": s.DNS.validate,
|
"dns": s.DNS.validate,
|
||||||
@@ -44,16 +38,15 @@ func (s *Settings) Validate(storage Storage) (err error) {
|
|||||||
"system": s.System.validate,
|
"system": s.System.validate,
|
||||||
"updater": s.Updater.Validate,
|
"updater": s.Updater.Validate,
|
||||||
"version": s.Version.validate,
|
"version": s.Version.validate,
|
||||||
// Pprof validation done in pprof constructor
|
|
||||||
"VPN": func() error {
|
"VPN": func() error {
|
||||||
return s.VPN.validate(storage)
|
return s.VPN.validate(allServers)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for name, validation := range nameToValidation {
|
for name, validation := range nameToValidation {
|
||||||
err = validation()
|
err = validation()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s settings: %w", name, err)
|
return fmt.Errorf("failed validating %s settings: %w", name, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,7 +67,6 @@ func (s *Settings) copy() (copied Settings) {
|
|||||||
Updater: s.Updater.copy(),
|
Updater: s.Updater.copy(),
|
||||||
Version: s.Version.copy(),
|
Version: s.Version.copy(),
|
||||||
VPN: s.VPN.copy(),
|
VPN: s.VPN.copy(),
|
||||||
Pprof: s.Pprof.Copy(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,11 +83,10 @@ func (s *Settings) MergeWith(other Settings) {
|
|||||||
s.Updater.mergeWith(other.Updater)
|
s.Updater.mergeWith(other.Updater)
|
||||||
s.Version.mergeWith(other.Version)
|
s.Version.mergeWith(other.Version)
|
||||||
s.VPN.mergeWith(other.VPN)
|
s.VPN.mergeWith(other.VPN)
|
||||||
s.Pprof.MergeWith(other.Pprof)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Settings) OverrideWith(other Settings,
|
func (s *Settings) OverrideWith(other Settings,
|
||||||
storage Storage) (err error) {
|
allServers models.AllServers) (err error) {
|
||||||
patchedSettings := s.copy()
|
patchedSettings := s.copy()
|
||||||
patchedSettings.ControlServer.overrideWith(other.ControlServer)
|
patchedSettings.ControlServer.overrideWith(other.ControlServer)
|
||||||
patchedSettings.DNS.overrideWith(other.DNS)
|
patchedSettings.DNS.overrideWith(other.DNS)
|
||||||
@@ -109,8 +100,7 @@ func (s *Settings) OverrideWith(other Settings,
|
|||||||
patchedSettings.Updater.overrideWith(other.Updater)
|
patchedSettings.Updater.overrideWith(other.Updater)
|
||||||
patchedSettings.Version.overrideWith(other.Version)
|
patchedSettings.Version.overrideWith(other.Version)
|
||||||
patchedSettings.VPN.overrideWith(other.VPN)
|
patchedSettings.VPN.overrideWith(other.VPN)
|
||||||
patchedSettings.Pprof.OverrideWith(other.Pprof)
|
err = patchedSettings.Validate(allServers)
|
||||||
err = patchedSettings.Validate(storage)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -128,10 +118,9 @@ func (s *Settings) SetDefaults() {
|
|||||||
s.PublicIP.setDefaults()
|
s.PublicIP.setDefaults()
|
||||||
s.Shadowsocks.setDefaults()
|
s.Shadowsocks.setDefaults()
|
||||||
s.System.setDefaults()
|
s.System.setDefaults()
|
||||||
|
s.Updater.SetDefaults()
|
||||||
s.Version.setDefaults()
|
s.Version.setDefaults()
|
||||||
s.VPN.setDefaults()
|
s.VPN.setDefaults()
|
||||||
s.Updater.SetDefaults(*s.VPN.Provider.Name)
|
|
||||||
s.Pprof.SetDefaults()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s Settings) String() string {
|
func (s Settings) String() string {
|
||||||
@@ -153,7 +142,6 @@ func (s Settings) toLinesNode() (node *gotree.Node) {
|
|||||||
node.AppendNode(s.PublicIP.toLinesNode())
|
node.AppendNode(s.PublicIP.toLinesNode())
|
||||||
node.AppendNode(s.Updater.toLinesNode())
|
node.AppendNode(s.Updater.toLinesNode())
|
||||||
node.AppendNode(s.Version.toLinesNode())
|
node.AppendNode(s.Version.toLinesNode())
|
||||||
node.AppendNode(s.Pprof.ToLinesNode())
|
|
||||||
|
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ func Test_Settings_String(t *testing.T) {
|
|||||||
| └── Log level: INFO
|
| └── Log level: INFO
|
||||||
├── Health settings:
|
├── Health settings:
|
||||||
| ├── Server listening address: 127.0.0.1:9999
|
| ├── Server listening address: 127.0.0.1:9999
|
||||||
| ├── Target address: cloudflare.com:443
|
| ├── Target address: github.com:443
|
||||||
| └── VPN wait durations:
|
| └── VPN wait durations:
|
||||||
| ├── Initial duration: 6s
|
| ├── Initial duration: 6s
|
||||||
| └── Additional duration: 5s
|
| └── Additional duration: 5s
|
||||||
@@ -75,7 +75,7 @@ func Test_Settings_String(t *testing.T) {
|
|||||||
├── HTTP proxy settings:
|
├── HTTP proxy settings:
|
||||||
| └── Enabled: no
|
| └── Enabled: no
|
||||||
├── Control server settings:
|
├── Control server settings:
|
||||||
| ├── Listening address: :8000
|
| ├── Listening port: 8000
|
||||||
| └── Logging: yes
|
| └── Logging: yes
|
||||||
├── OS Alpine settings:
|
├── OS Alpine settings:
|
||||||
| ├── Process UID: 1000
|
| ├── Process UID: 1000
|
||||||
|
|||||||
@@ -3,14 +3,15 @@ package settings
|
|||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/provider/surfshark/servers"
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
func surfsharkRetroRegion(selection ServerSelection) (
|
func surfsharkRetroRegion(selection ServerSelection) (
|
||||||
updatedSelection ServerSelection) {
|
updatedSelection ServerSelection) {
|
||||||
locationData := servers.LocationData()
|
locationData := constants.SurfsharkLocationData()
|
||||||
|
|
||||||
retroToLocation := make(map[string]servers.ServerLocation, len(locationData))
|
retroToLocation := make(map[string]models.SurfsharkLocationData, len(locationData))
|
||||||
for _, data := range locationData {
|
for _, data := range locationData {
|
||||||
if data.RetroLoc == "" {
|
if data.RetroLoc == "" {
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import (
|
|||||||
|
|
||||||
// System contains settings to configure system related elements.
|
// System contains settings to configure system related elements.
|
||||||
type System struct {
|
type System struct {
|
||||||
PUID *uint32
|
PUID *uint16
|
||||||
PGID *uint32
|
PGID *uint16
|
||||||
Timezone string
|
Timezone string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -19,28 +19,28 @@ func (s System) validate() (err error) {
|
|||||||
|
|
||||||
func (s *System) copy() (copied System) {
|
func (s *System) copy() (copied System) {
|
||||||
return System{
|
return System{
|
||||||
PUID: helpers.CopyUint32Ptr(s.PUID),
|
PUID: helpers.CopyUint16Ptr(s.PUID),
|
||||||
PGID: helpers.CopyUint32Ptr(s.PGID),
|
PGID: helpers.CopyUint16Ptr(s.PGID),
|
||||||
Timezone: s.Timezone,
|
Timezone: s.Timezone,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *System) mergeWith(other System) {
|
func (s *System) mergeWith(other System) {
|
||||||
s.PUID = helpers.MergeWithUint32(s.PUID, other.PUID)
|
s.PUID = helpers.MergeWithUint16(s.PUID, other.PUID)
|
||||||
s.PGID = helpers.MergeWithUint32(s.PGID, other.PGID)
|
s.PGID = helpers.MergeWithUint16(s.PGID, other.PGID)
|
||||||
s.Timezone = helpers.MergeWithString(s.Timezone, other.Timezone)
|
s.Timezone = helpers.MergeWithString(s.Timezone, other.Timezone)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *System) overrideWith(other System) {
|
func (s *System) overrideWith(other System) {
|
||||||
s.PUID = helpers.OverrideWithUint32(s.PUID, other.PUID)
|
s.PUID = helpers.OverrideWithUint16(s.PUID, other.PUID)
|
||||||
s.PGID = helpers.OverrideWithUint32(s.PGID, other.PGID)
|
s.PGID = helpers.OverrideWithUint16(s.PGID, other.PGID)
|
||||||
s.Timezone = helpers.OverrideWithString(s.Timezone, other.Timezone)
|
s.Timezone = helpers.OverrideWithString(s.Timezone, other.Timezone)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *System) setDefaults() {
|
func (s *System) setDefaults() {
|
||||||
const defaultID = 1000
|
const defaultID = 1000
|
||||||
s.PUID = helpers.DefaultUint32(s.PUID, defaultID)
|
s.PUID = helpers.DefaultUint16(s.PUID, defaultID)
|
||||||
s.PGID = helpers.DefaultUint32(s.PGID, defaultID)
|
s.PGID = helpers.DefaultUint16(s.PGID, defaultID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s System) String() string {
|
func (s System) String() string {
|
||||||
|
|||||||
@@ -2,11 +2,12 @@ package settings
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
"github.com/qdm12/gotree"
|
"github.com/qdm12/gotree"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -20,15 +21,16 @@ type Updater struct {
|
|||||||
Period *time.Duration
|
Period *time.Duration
|
||||||
// DNSAddress is the DNS server address to use
|
// DNSAddress is the DNS server address to use
|
||||||
// to resolve VPN server hostnames to IP addresses.
|
// to resolve VPN server hostnames to IP addresses.
|
||||||
// It cannot be the empty string in the internal state.
|
// It cannot be nil in the internal state.
|
||||||
DNSAddress string
|
DNSAddress net.IP
|
||||||
// MinRatio is the minimum ratio of servers to
|
|
||||||
// find per provider, compared to the total current
|
|
||||||
// number of servers. It defaults to 0.8.
|
|
||||||
MinRatio float64
|
|
||||||
// Providers is the list of VPN service providers
|
// Providers is the list of VPN service providers
|
||||||
// to update server information for.
|
// to update server information for.
|
||||||
Providers []string
|
Providers []string
|
||||||
|
// CLI is to precise the updater is running in CLI
|
||||||
|
// mode. This is set automatically and cannot be set
|
||||||
|
// by settings sources. It cannot be nil in the
|
||||||
|
// internal state.
|
||||||
|
CLI *bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u Updater) Validate() (err error) {
|
func (u Updater) Validate() (err error) {
|
||||||
@@ -38,23 +40,21 @@ func (u Updater) Validate() (err error) {
|
|||||||
ErrUpdaterPeriodTooSmall, *u.Period, minPeriod)
|
ErrUpdaterPeriodTooSmall, *u.Period, minPeriod)
|
||||||
}
|
}
|
||||||
|
|
||||||
if u.MinRatio <= 0 || u.MinRatio > 1 {
|
for i, provider := range u.Providers {
|
||||||
return fmt.Errorf("%w: %.2f must be between 0+ and 1",
|
|
||||||
ErrMinRatioNotValid, u.MinRatio)
|
|
||||||
}
|
|
||||||
|
|
||||||
validProviders := providers.All()
|
|
||||||
for _, provider := range u.Providers {
|
|
||||||
valid := false
|
valid := false
|
||||||
for _, validProvider := range validProviders {
|
for _, validProvider := range constants.AllProviders() {
|
||||||
|
if validProvider == constants.Custom {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if provider == validProvider {
|
if provider == validProvider {
|
||||||
valid = true
|
valid = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !valid {
|
if !valid {
|
||||||
return fmt.Errorf("%w: %q can only be one of %s",
|
return fmt.Errorf("%w: %s at index %d",
|
||||||
ErrVPNProviderNameNotValid, provider, helpers.ChoicesOrString(validProviders))
|
ErrVPNProviderNameNotValid, provider, i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,9 +64,9 @@ func (u Updater) Validate() (err error) {
|
|||||||
func (u *Updater) copy() (copied Updater) {
|
func (u *Updater) copy() (copied Updater) {
|
||||||
return Updater{
|
return Updater{
|
||||||
Period: helpers.CopyDurationPtr(u.Period),
|
Period: helpers.CopyDurationPtr(u.Period),
|
||||||
DNSAddress: u.DNSAddress,
|
DNSAddress: helpers.CopyIP(u.DNSAddress),
|
||||||
MinRatio: u.MinRatio,
|
|
||||||
Providers: helpers.CopyStringSlice(u.Providers),
|
Providers: helpers.CopyStringSlice(u.Providers),
|
||||||
|
CLI: u.CLI,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,9 +74,9 @@ func (u *Updater) copy() (copied Updater) {
|
|||||||
// unset field of the receiver settings object.
|
// unset field of the receiver settings object.
|
||||||
func (u *Updater) mergeWith(other Updater) {
|
func (u *Updater) mergeWith(other Updater) {
|
||||||
u.Period = helpers.MergeWithDuration(u.Period, other.Period)
|
u.Period = helpers.MergeWithDuration(u.Period, other.Period)
|
||||||
u.DNSAddress = helpers.MergeWithString(u.DNSAddress, other.DNSAddress)
|
u.DNSAddress = helpers.MergeWithIP(u.DNSAddress, other.DNSAddress)
|
||||||
u.MinRatio = helpers.MergeWithFloat64(u.MinRatio, other.MinRatio)
|
|
||||||
u.Providers = helpers.MergeStringSlices(u.Providers, other.Providers)
|
u.Providers = helpers.MergeStringSlices(u.Providers, other.Providers)
|
||||||
|
u.CLI = helpers.MergeWithBool(u.CLI, other.CLI)
|
||||||
}
|
}
|
||||||
|
|
||||||
// overrideWith overrides fields of the receiver
|
// overrideWith overrides fields of the receiver
|
||||||
@@ -84,23 +84,15 @@ func (u *Updater) mergeWith(other Updater) {
|
|||||||
// settings.
|
// settings.
|
||||||
func (u *Updater) overrideWith(other Updater) {
|
func (u *Updater) overrideWith(other Updater) {
|
||||||
u.Period = helpers.OverrideWithDuration(u.Period, other.Period)
|
u.Period = helpers.OverrideWithDuration(u.Period, other.Period)
|
||||||
u.DNSAddress = helpers.OverrideWithString(u.DNSAddress, other.DNSAddress)
|
u.DNSAddress = helpers.OverrideWithIP(u.DNSAddress, other.DNSAddress)
|
||||||
u.MinRatio = helpers.OverrideWithFloat64(u.MinRatio, other.MinRatio)
|
|
||||||
u.Providers = helpers.OverrideWithStringSlice(u.Providers, other.Providers)
|
u.Providers = helpers.OverrideWithStringSlice(u.Providers, other.Providers)
|
||||||
|
u.CLI = helpers.MergeWithBool(u.CLI, other.CLI)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *Updater) SetDefaults(vpnProvider string) {
|
func (u *Updater) SetDefaults() {
|
||||||
u.Period = helpers.DefaultDuration(u.Period, 0)
|
u.Period = helpers.DefaultDuration(u.Period, 0)
|
||||||
u.DNSAddress = helpers.DefaultString(u.DNSAddress, "1.1.1.1:53")
|
u.DNSAddress = helpers.DefaultIP(u.DNSAddress, net.IPv4(1, 1, 1, 1))
|
||||||
|
u.CLI = helpers.DefaultBool(u.CLI, false)
|
||||||
if u.MinRatio == 0 {
|
|
||||||
const defaultMinRatio = 0.8
|
|
||||||
u.MinRatio = defaultMinRatio
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(u.Providers) == 0 && vpnProvider != providers.Custom {
|
|
||||||
u.Providers = []string{vpnProvider}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u Updater) String() string {
|
func (u Updater) String() string {
|
||||||
@@ -108,15 +100,18 @@ func (u Updater) String() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u Updater) toLinesNode() (node *gotree.Node) {
|
func (u Updater) toLinesNode() (node *gotree.Node) {
|
||||||
if *u.Period == 0 || len(u.Providers) == 0 {
|
if *u.Period == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
node = gotree.New("Server data updater settings:")
|
node = gotree.New("Server data updater settings:")
|
||||||
node.Appendf("Update period: %s", *u.Period)
|
node.Appendf("Update period: %s", *u.Period)
|
||||||
node.Appendf("DNS address: %s", u.DNSAddress)
|
node.Appendf("DNS address: %s", u.DNSAddress)
|
||||||
node.Appendf("Minimum ratio: %.1f", u.MinRatio)
|
|
||||||
node.Appendf("Providers to update: %s", strings.Join(u.Providers, ", "))
|
node.Appendf("Providers to update: %s", strings.Join(u.Providers, ", "))
|
||||||
|
|
||||||
|
if *u.CLI {
|
||||||
|
node.Appendf("CLI mode: enabled")
|
||||||
|
}
|
||||||
|
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,129 +0,0 @@
|
|||||||
package validation
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sort"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
|
||||||
)
|
|
||||||
|
|
||||||
func sortedInsert(ss []string, s string) []string {
|
|
||||||
i := sort.SearchStrings(ss, s)
|
|
||||||
ss = append(ss, "")
|
|
||||||
copy(ss[i+1:], ss[i:])
|
|
||||||
ss[i] = s
|
|
||||||
return ss
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExtractCountries(servers []models.Server) (values []string) {
|
|
||||||
seen := make(map[string]struct{}, len(servers))
|
|
||||||
values = make([]string, 0, len(servers))
|
|
||||||
for _, server := range servers {
|
|
||||||
value := server.Country
|
|
||||||
if value == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
_, alreadySeen := seen[value]
|
|
||||||
if alreadySeen {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
seen[value] = struct{}{}
|
|
||||||
|
|
||||||
values = sortedInsert(values, value)
|
|
||||||
}
|
|
||||||
return values
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExtractRegions(servers []models.Server) (values []string) {
|
|
||||||
seen := make(map[string]struct{}, len(servers))
|
|
||||||
values = make([]string, 0, len(servers))
|
|
||||||
for _, server := range servers {
|
|
||||||
value := server.Region
|
|
||||||
if value == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
_, alreadySeen := seen[value]
|
|
||||||
if alreadySeen {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
seen[value] = struct{}{}
|
|
||||||
|
|
||||||
values = sortedInsert(values, value)
|
|
||||||
}
|
|
||||||
return values
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExtractCities(servers []models.Server) (values []string) {
|
|
||||||
seen := make(map[string]struct{}, len(servers))
|
|
||||||
values = make([]string, 0, len(servers))
|
|
||||||
for _, server := range servers {
|
|
||||||
value := server.City
|
|
||||||
if value == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
_, alreadySeen := seen[value]
|
|
||||||
if alreadySeen {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
seen[value] = struct{}{}
|
|
||||||
|
|
||||||
values = sortedInsert(values, value)
|
|
||||||
}
|
|
||||||
return values
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExtractISPs(servers []models.Server) (values []string) {
|
|
||||||
seen := make(map[string]struct{}, len(servers))
|
|
||||||
values = make([]string, 0, len(servers))
|
|
||||||
for _, server := range servers {
|
|
||||||
value := server.ISP
|
|
||||||
if value == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
_, alreadySeen := seen[value]
|
|
||||||
if alreadySeen {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
seen[value] = struct{}{}
|
|
||||||
|
|
||||||
values = sortedInsert(values, value)
|
|
||||||
}
|
|
||||||
return values
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExtractServerNames(servers []models.Server) (values []string) {
|
|
||||||
seen := make(map[string]struct{}, len(servers))
|
|
||||||
values = make([]string, 0, len(servers))
|
|
||||||
for _, server := range servers {
|
|
||||||
value := server.ServerName
|
|
||||||
if value == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
_, alreadySeen := seen[value]
|
|
||||||
if alreadySeen {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
seen[value] = struct{}{}
|
|
||||||
|
|
||||||
values = sortedInsert(values, value)
|
|
||||||
}
|
|
||||||
return values
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExtractHostnames(servers []models.Server) (values []string) {
|
|
||||||
seen := make(map[string]struct{}, len(servers))
|
|
||||||
values = make([]string, 0, len(servers))
|
|
||||||
for _, server := range servers {
|
|
||||||
value := server.Hostname
|
|
||||||
if value == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
_, alreadySeen := seen[value]
|
|
||||||
if alreadySeen {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
seen[value] = struct{}{}
|
|
||||||
|
|
||||||
values = sortedInsert(values, value)
|
|
||||||
}
|
|
||||||
return values
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
package validation
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/qdm12/gluetun/internal/provider/surfshark/servers"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TODO remove in v4.
|
|
||||||
func SurfsharkRetroLocChoices() (choices []string) {
|
|
||||||
locationData := servers.LocationData()
|
|
||||||
choices = make([]string, 0, len(locationData))
|
|
||||||
seen := make(map[string]struct{}, len(locationData))
|
|
||||||
for _, data := range locationData {
|
|
||||||
if _, ok := seen[data.RetroLoc]; ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
seen[data.RetroLoc] = struct{}{}
|
|
||||||
choices = sortedInsert(choices, data.RetroLoc)
|
|
||||||
}
|
|
||||||
|
|
||||||
return choices
|
|
||||||
}
|
|
||||||
@@ -5,7 +5,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||||
"github.com/qdm12/gluetun/internal/constants/vpn"
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
"github.com/qdm12/gotree"
|
"github.com/qdm12/gotree"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -20,28 +21,28 @@ type VPN struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO v4 remove pointer for receiver (because of Surfshark).
|
// TODO v4 remove pointer for receiver (because of Surfshark).
|
||||||
func (v *VPN) validate(storage Storage) (err error) {
|
func (v *VPN) validate(allServers models.AllServers) (err error) {
|
||||||
// Validate Type
|
// Validate Type
|
||||||
validVPNTypes := []string{vpn.OpenVPN, vpn.Wireguard}
|
validVPNTypes := []string{constants.OpenVPN, constants.Wireguard}
|
||||||
if !helpers.IsOneOf(v.Type, validVPNTypes...) {
|
if !helpers.IsOneOf(v.Type, validVPNTypes...) {
|
||||||
return fmt.Errorf("%w: %q and can only be one of %s",
|
return fmt.Errorf("%w: %q and can only be one of %s",
|
||||||
ErrVPNTypeNotValid, v.Type, strings.Join(validVPNTypes, ", "))
|
ErrVPNTypeNotValid, v.Type, strings.Join(validVPNTypes, ", "))
|
||||||
}
|
}
|
||||||
|
|
||||||
err = v.Provider.validate(v.Type, storage)
|
err = v.Provider.validate(v.Type, allServers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("provider settings: %w", err)
|
return fmt.Errorf("provider settings validation failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if v.Type == vpn.OpenVPN {
|
if v.Type == constants.OpenVPN {
|
||||||
err := v.OpenVPN.validate(*v.Provider.Name)
|
err := v.OpenVPN.validate(*v.Provider.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("OpenVPN settings: %w", err)
|
return fmt.Errorf("OpenVPN settings validation failed: %w", err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
err := v.Wireguard.validate(*v.Provider.Name)
|
err := v.Wireguard.validate(*v.Provider.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Wireguard settings: %w", err)
|
return fmt.Errorf("Wireguard settings validation failed: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,7 +73,7 @@ func (v *VPN) overrideWith(other VPN) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (v *VPN) setDefaults() {
|
func (v *VPN) setDefaults() {
|
||||||
v.Type = helpers.DefaultString(v.Type, vpn.OpenVPN)
|
v.Type = helpers.DefaultString(v.Type, constants.OpenVPN)
|
||||||
v.Provider.setDefaults()
|
v.Provider.setDefaults()
|
||||||
v.OpenVPN.setDefaults(*v.Provider.Name)
|
v.OpenVPN.setDefaults(*v.Provider.Name)
|
||||||
v.Wireguard.setDefaults()
|
v.Wireguard.setDefaults()
|
||||||
@@ -87,7 +88,7 @@ func (v VPN) toLinesNode() (node *gotree.Node) {
|
|||||||
|
|
||||||
node.AppendNode(v.Provider.toLinesNode())
|
node.AppendNode(v.Provider.toLinesNode())
|
||||||
|
|
||||||
if v.Type == vpn.OpenVPN {
|
if v.Type == constants.OpenVPN {
|
||||||
node.AppendNode(v.OpenVPN.toLinesNode())
|
node.AppendNode(v.OpenVPN.toLinesNode())
|
||||||
} else {
|
} else {
|
||||||
node.AppendNode(v.Wireguard.toLinesNode())
|
node.AppendNode(v.Wireguard.toLinesNode())
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
"github.com/qdm12/gotree"
|
"github.com/qdm12/gotree"
|
||||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
)
|
)
|
||||||
@@ -35,10 +35,10 @@ var regexpInterfaceName = regexp.MustCompile(`^[a-zA-Z0-9_]+$`)
|
|||||||
// It should only be ran if the VPN type chosen is Wireguard.
|
// It should only be ran if the VPN type chosen is Wireguard.
|
||||||
func (w Wireguard) validate(vpnProvider string) (err error) {
|
func (w Wireguard) validate(vpnProvider string) (err error) {
|
||||||
if !helpers.IsOneOf(vpnProvider,
|
if !helpers.IsOneOf(vpnProvider,
|
||||||
providers.Custom,
|
constants.Custom,
|
||||||
providers.Ivpn,
|
constants.Ivpn,
|
||||||
providers.Mullvad,
|
constants.Mullvad,
|
||||||
providers.Windscribe,
|
constants.Windscribe,
|
||||||
) {
|
) {
|
||||||
// do not validate for VPN provider not supporting Wireguard
|
// do not validate for VPN provider not supporting Wireguard
|
||||||
return nil
|
return nil
|
||||||
@@ -50,14 +50,14 @@ func (w Wireguard) validate(vpnProvider string) (err error) {
|
|||||||
}
|
}
|
||||||
_, err = wgtypes.ParseKey(*w.PrivateKey)
|
_, err = wgtypes.ParseKey(*w.PrivateKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("private key is not valid: %w", err)
|
return fmt.Errorf("%w: %s", ErrWireguardPrivateKeyNotValid, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate PreSharedKey
|
// Validate PreSharedKey
|
||||||
if *w.PreSharedKey != "" { // Note: this is optional
|
if *w.PreSharedKey != "" { // Note: this is optional
|
||||||
_, err = wgtypes.ParseKey(*w.PreSharedKey)
|
_, err = wgtypes.ParseKey(*w.PreSharedKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("pre-shared key is not valid: %w", err)
|
return fmt.Errorf("%w: %s", ErrWireguardPreSharedKeyNotValid, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
"github.com/qdm12/gotree"
|
"github.com/qdm12/gotree"
|
||||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
)
|
)
|
||||||
@@ -36,8 +36,8 @@ type WireguardSelection struct {
|
|||||||
func (w WireguardSelection) validate(vpnProvider string) (err error) {
|
func (w WireguardSelection) validate(vpnProvider string) (err error) {
|
||||||
// Validate EndpointIP
|
// Validate EndpointIP
|
||||||
switch vpnProvider {
|
switch vpnProvider {
|
||||||
case providers.Ivpn, providers.Mullvad, providers.Windscribe: // endpoint IP addresses are baked in
|
case constants.Ivpn, constants.Mullvad, constants.Windscribe: // endpoint IP addresses are baked in
|
||||||
case providers.Custom:
|
case constants.Custom:
|
||||||
if len(w.EndpointIP) == 0 {
|
if len(w.EndpointIP) == 0 {
|
||||||
return ErrWireguardEndpointIPNotSet
|
return ErrWireguardEndpointIPNotSet
|
||||||
}
|
}
|
||||||
@@ -47,23 +47,23 @@ func (w WireguardSelection) validate(vpnProvider string) (err error) {
|
|||||||
// Validate EndpointPort
|
// Validate EndpointPort
|
||||||
switch vpnProvider {
|
switch vpnProvider {
|
||||||
// EndpointPort is required
|
// EndpointPort is required
|
||||||
case providers.Custom:
|
case constants.Custom:
|
||||||
if *w.EndpointPort == 0 {
|
if *w.EndpointPort == 0 {
|
||||||
return ErrWireguardEndpointPortNotSet
|
return ErrWireguardEndpointPortNotSet
|
||||||
}
|
}
|
||||||
case providers.Ivpn, providers.Mullvad, providers.Windscribe:
|
case constants.Ivpn, constants.Mullvad, constants.Windscribe:
|
||||||
// EndpointPort is optional and can be 0
|
// EndpointPort is optional and can be 0
|
||||||
if *w.EndpointPort == 0 {
|
if *w.EndpointPort == 0 {
|
||||||
break // no custom endpoint port set
|
break // no custom endpoint port set
|
||||||
}
|
}
|
||||||
if vpnProvider == providers.Mullvad {
|
if vpnProvider == constants.Mullvad {
|
||||||
break // no restriction on custom endpoint port value
|
break // no restriction on custom endpoint port value
|
||||||
}
|
}
|
||||||
var allowed []uint16
|
var allowed []uint16
|
||||||
switch vpnProvider {
|
switch vpnProvider {
|
||||||
case providers.Ivpn:
|
case constants.Ivpn:
|
||||||
allowed = []uint16{2049, 2050, 53, 30587, 41893, 48574, 58237}
|
allowed = []uint16{2049, 2050, 53, 30587, 41893, 48574, 58237}
|
||||||
case providers.Windscribe:
|
case constants.Windscribe:
|
||||||
allowed = []uint16{53, 80, 123, 443, 1194, 65142}
|
allowed = []uint16{53, 80, 123, 443, 1194, 65142}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,8 +78,8 @@ func (w WireguardSelection) validate(vpnProvider string) (err error) {
|
|||||||
|
|
||||||
// Validate PublicKey
|
// Validate PublicKey
|
||||||
switch vpnProvider {
|
switch vpnProvider {
|
||||||
case providers.Ivpn, providers.Mullvad, providers.Windscribe: // public keys are baked in
|
case constants.Ivpn, constants.Mullvad, constants.Windscribe: // public keys are baked in
|
||||||
case providers.Custom:
|
case constants.Custom:
|
||||||
if w.PublicKey == "" {
|
if w.PublicKey == "" {
|
||||||
return ErrWireguardPublicKeyNotSet
|
return ErrWireguardPublicKeyNotSet
|
||||||
}
|
}
|
||||||
|
|||||||
9
internal/configuration/sources/env/dns.go
vendored
9
internal/configuration/sources/env/dns.go
vendored
@@ -3,6 +3,7 @@ package env
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||||
)
|
)
|
||||||
@@ -20,26 +21,26 @@ func (r *Reader) readDNS() (dns settings.DNS, err error) {
|
|||||||
|
|
||||||
dns.DoT, err = r.readDoT()
|
dns.DoT, err = r.readDoT()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return dns, fmt.Errorf("DoT settings: %w", err)
|
return dns, fmt.Errorf("cannot read DoT settings: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return dns, nil
|
return dns, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reader) readDNSServerAddress() (address net.IP, err error) {
|
func (r *Reader) readDNSServerAddress() (address net.IP, err error) {
|
||||||
key, s := r.getEnvWithRetro("DNS_ADDRESS", "DNS_PLAINTEXT_ADDRESS")
|
s := os.Getenv("DNS_PLAINTEXT_ADDRESS")
|
||||||
if s == "" {
|
if s == "" {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
address = net.ParseIP(s)
|
address = net.ParseIP(s)
|
||||||
if address == nil {
|
if address == nil {
|
||||||
return nil, fmt.Errorf("environment variable %s: %w: %s", key, ErrIPAddressParse, s)
|
return nil, fmt.Errorf("environment variable DNS_PLAINTEXT_ADDRESS: %w: %s", ErrIPAddressParse, s)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO remove in v4
|
// TODO remove in v4
|
||||||
if !address.Equal(net.IPv4(127, 0, 0, 1)) { //nolint:gomnd
|
if !address.Equal(net.IPv4(127, 0, 0, 1)) { //nolint:gomnd
|
||||||
r.warner.Warn(key + " is set to " + s +
|
r.warner.Warn("DNS_PLAINTEXT_ADDRESS is set to " + s +
|
||||||
" so the DNS over TLS (DoT) server will not be used." +
|
" so the DNS over TLS (DoT) server will not be used." +
|
||||||
" The default value changed to 127.0.0.1 so it uses the internal DoT server." +
|
" The default value changed to 127.0.0.1 so it uses the internal DoT server." +
|
||||||
" If the DoT server fails to start, the IPv4 address of the first plaintext DNS server" +
|
" If the DoT server fails to start, the IPv4 address of the first plaintext DNS server" +
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||||
"github.com/qdm12/govalid/binary"
|
|
||||||
"inet.af/netaddr"
|
"inet.af/netaddr"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -17,7 +16,7 @@ func (r *Reader) readDNSBlacklist() (blacklist settings.DNSBlacklist, err error)
|
|||||||
|
|
||||||
blacklist.BlockSurveillance, err = r.readBlockSurveillance()
|
blacklist.BlockSurveillance, err = r.readBlockSurveillance()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return blacklist, err
|
return blacklist, fmt.Errorf("environment variable BLOCK_MALICIOUS: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
blacklist.BlockAds, err = envToBoolPtr("BLOCK_ADS")
|
blacklist.BlockAds, err = envToBoolPtr("BLOCK_ADS")
|
||||||
@@ -37,18 +36,22 @@ func (r *Reader) readDNSBlacklist() (blacklist settings.DNSBlacklist, err error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reader) readBlockSurveillance() (blocked *bool, err error) {
|
func (r *Reader) readBlockSurveillance() (blocked *bool, err error) {
|
||||||
key, value := r.getEnvWithRetro("BLOCK_SURVEILLANCE", "BLOCK_NSA")
|
blocked, err = envToBoolPtr("BLOCK_SURVEILLANCE")
|
||||||
if value == "" {
|
|
||||||
return nil, nil //nolint:nilnil
|
|
||||||
}
|
|
||||||
|
|
||||||
blocked = new(bool)
|
|
||||||
*blocked, err = binary.Validate(value)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("environment variable %s: %w", key, err)
|
return nil, fmt.Errorf("environment variable BLOCK_SURVEILLANCE: %w", err)
|
||||||
|
} else if blocked != nil {
|
||||||
|
return blocked, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return blocked, nil
|
blocked, err = envToBoolPtr("BLOCK_NSA")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("environment variable BLOCK_NSA: %w", err)
|
||||||
|
} else if blocked != nil {
|
||||||
|
r.onRetroActive("BLOCK_NSA", "BLOCK_SURVEILLANCE")
|
||||||
|
return blocked, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil //nolint:nilnil
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
20
internal/configuration/sources/env/firewall.go
vendored
20
internal/configuration/sources/env/firewall.go
vendored
@@ -22,8 +22,16 @@ func (r *Reader) readFirewall() (firewall settings.Firewall, err error) {
|
|||||||
return firewall, fmt.Errorf("environment variable FIREWALL_INPUT_PORTS: %w", err)
|
return firewall, fmt.Errorf("environment variable FIREWALL_INPUT_PORTS: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
outboundSubnetsKey, _ := r.getEnvWithRetro("FIREWALL_OUTBOUND_SUBNETS", "EXTRA_SUBNETS")
|
outboundSubnetsKey := "FIREWALL_OUTBOUND_SUBNETS"
|
||||||
outboundSubnetStrings := envToCSV(outboundSubnetsKey)
|
outboundSubnetStrings := envToCSV(outboundSubnetsKey)
|
||||||
|
if len(outboundSubnetStrings) == 0 {
|
||||||
|
// Retro-compatibility
|
||||||
|
outboundSubnetStrings = envToCSV("EXTRA_SUBNETS")
|
||||||
|
if len(outboundSubnetStrings) > 0 {
|
||||||
|
outboundSubnetsKey = "EXTRA_SUBNETS"
|
||||||
|
r.onRetroActive("EXTRA_SUBNETS", "FIREWALL_OUTBOUND_SUBNETS")
|
||||||
|
}
|
||||||
|
}
|
||||||
firewall.OutboundSubnets, err = stringsToIPNets(outboundSubnetStrings)
|
firewall.OutboundSubnets, err = stringsToIPNets(outboundSubnetStrings)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return firewall, fmt.Errorf("environment variable %s: %w", outboundSubnetsKey, err)
|
return firewall, fmt.Errorf("environment variable %s: %w", outboundSubnetsKey, err)
|
||||||
@@ -55,7 +63,8 @@ func stringsToPorts(ss []string) (ports []uint16, err error) {
|
|||||||
for i, s := range ss {
|
for i, s := range ss {
|
||||||
port, err := strconv.Atoi(s)
|
port, err := strconv.Atoi(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%w: %s: %s", ErrPortParsing, s, err)
|
return nil, fmt.Errorf("%w: %s: %s",
|
||||||
|
ErrPortParsing, s, err)
|
||||||
} else if port < 1 || port > 65535 {
|
} else if port < 1 || port > 65535 {
|
||||||
return nil, fmt.Errorf("%w: must be between 1 and 65535: %d",
|
return nil, fmt.Errorf("%w: must be between 1 and 65535: %d",
|
||||||
ErrPortValue, port)
|
ErrPortValue, port)
|
||||||
@@ -65,6 +74,10 @@ func stringsToPorts(ss []string) (ports []uint16, err error) {
|
|||||||
return ports, nil
|
return ports, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrIPNetParsing = errors.New("cannot parse IP network")
|
||||||
|
)
|
||||||
|
|
||||||
func stringsToIPNets(ss []string) (ipNets []net.IPNet, err error) {
|
func stringsToIPNets(ss []string) (ipNets []net.IPNet, err error) {
|
||||||
if len(ss) == 0 {
|
if len(ss) == 0 {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
@@ -73,7 +86,8 @@ func stringsToIPNets(ss []string) (ipNets []net.IPNet, err error) {
|
|||||||
for i, s := range ss {
|
for i, s := range ss {
|
||||||
ip, ipNet, err := net.ParseCIDR(s)
|
ip, ipNet, err := net.ParseCIDR(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("cannot parse IP network %q: %w", s, err)
|
return nil, fmt.Errorf("%w: %s: %s",
|
||||||
|
ErrIPNetParsing, s, err)
|
||||||
}
|
}
|
||||||
ipNet.IP = ip
|
ipNet.IP = ip
|
||||||
ipNets[i] = *ipNet
|
ipNets[i] = *ipNet
|
||||||
|
|||||||
23
internal/configuration/sources/env/health.go
vendored
23
internal/configuration/sources/env/health.go
vendored
@@ -2,14 +2,18 @@ package env
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (r *Reader) ReadHealth() (health settings.Health, err error) {
|
func (r *Reader) ReadHealth() (health settings.Health, err error) {
|
||||||
health.ServerAddress = getCleanedEnv("HEALTH_SERVER_ADDRESS")
|
health.ServerAddress = os.Getenv("HEALTH_SERVER_ADDRESS")
|
||||||
_, health.TargetAddress = r.getEnvWithRetro("HEALTH_TARGET_ADDRESS", "HEALTH_ADDRESS_TO_PING")
|
health.TargetAddress = os.Getenv("HEALTH_ADDRESS_TO_PING")
|
||||||
|
if health.TargetAddress == "" {
|
||||||
|
health.TargetAddress = os.Getenv("HEALTH_TARGET_ADDRESS")
|
||||||
|
}
|
||||||
|
|
||||||
health.VPN.Initial, err = r.readDurationWithRetro(
|
health.VPN.Initial, err = r.readDurationWithRetro(
|
||||||
"HEALTH_VPN_DURATION_INITIAL",
|
"HEALTH_VPN_DURATION_INITIAL",
|
||||||
@@ -18,7 +22,7 @@ func (r *Reader) ReadHealth() (health settings.Health, err error) {
|
|||||||
return health, err
|
return health, err
|
||||||
}
|
}
|
||||||
|
|
||||||
health.VPN.Addition, err = r.readDurationWithRetro(
|
health.VPN.Initial, err = r.readDurationWithRetro(
|
||||||
"HEALTH_VPN_DURATION_ADDITION",
|
"HEALTH_VPN_DURATION_ADDITION",
|
||||||
"HEALTH_OPENVPN_DURATION_ADDITION")
|
"HEALTH_OPENVPN_DURATION_ADDITION")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -29,15 +33,22 @@ func (r *Reader) ReadHealth() (health settings.Health, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reader) readDurationWithRetro(envKey, retroEnvKey string) (d *time.Duration, err error) {
|
func (r *Reader) readDurationWithRetro(envKey, retroEnvKey string) (d *time.Duration, err error) {
|
||||||
envKey, s := r.getEnvWithRetro(envKey, retroEnvKey)
|
s := os.Getenv(envKey)
|
||||||
if s == "" {
|
if s == "" {
|
||||||
return nil, nil //nolint:nilnil
|
s = os.Getenv(retroEnvKey)
|
||||||
|
if s == "" {
|
||||||
|
return nil, nil //nolint:nilnil
|
||||||
|
}
|
||||||
|
r.onRetroActive(envKey, retroEnvKey)
|
||||||
|
envKey = retroEnvKey
|
||||||
}
|
}
|
||||||
|
|
||||||
d = new(time.Duration)
|
d = new(time.Duration)
|
||||||
*d, err = time.ParseDuration(s)
|
*d, err = time.ParseDuration(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("environment variable %s: %w", envKey, err)
|
return nil, fmt.Errorf(
|
||||||
|
"environment variable %s: %w",
|
||||||
|
envKey, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return d, nil
|
return d, nil
|
||||||
|
|||||||
56
internal/configuration/sources/env/helpers.go
vendored
56
internal/configuration/sources/env/helpers.go
vendored
@@ -1,6 +1,8 @@
|
|||||||
package env
|
package env
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -11,43 +13,16 @@ import (
|
|||||||
"github.com/qdm12/govalid/integer"
|
"github.com/qdm12/govalid/integer"
|
||||||
)
|
)
|
||||||
|
|
||||||
// getCleanedEnv returns an environment variable value with
|
|
||||||
// surrounding spaces and trailing new line characters removed.
|
|
||||||
func getCleanedEnv(envKey string) (value string) {
|
|
||||||
value = os.Getenv(envKey)
|
|
||||||
value = strings.TrimSpace(value)
|
|
||||||
value = strings.TrimSuffix(value, "\r\n")
|
|
||||||
value = strings.TrimSuffix(value, "\n")
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
func envToCSV(envKey string) (values []string) {
|
func envToCSV(envKey string) (values []string) {
|
||||||
csv := getCleanedEnv(envKey)
|
csv := os.Getenv(envKey)
|
||||||
if csv == "" {
|
if csv == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return lowerAndSplit(csv)
|
return lowerAndSplit(csv)
|
||||||
}
|
}
|
||||||
|
|
||||||
func envToInt(envKey string) (n int, err error) {
|
|
||||||
s := getCleanedEnv(envKey)
|
|
||||||
if s == "" {
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
return strconv.Atoi(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
func envToFloat64(envKey string) (f float64, err error) {
|
|
||||||
s := getCleanedEnv(envKey)
|
|
||||||
if s == "" {
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
const bits = 64
|
|
||||||
return strconv.ParseFloat(s, bits)
|
|
||||||
}
|
|
||||||
|
|
||||||
func envToStringPtr(envKey string) (stringPtr *string) {
|
func envToStringPtr(envKey string) (stringPtr *string) {
|
||||||
s := getCleanedEnv(envKey)
|
s := os.Getenv(envKey)
|
||||||
if s == "" {
|
if s == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -55,7 +30,7 @@ func envToStringPtr(envKey string) (stringPtr *string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func envToBoolPtr(envKey string) (boolPtr *bool, err error) {
|
func envToBoolPtr(envKey string) (boolPtr *bool, err error) {
|
||||||
s := getCleanedEnv(envKey)
|
s := os.Getenv(envKey)
|
||||||
if s == "" {
|
if s == "" {
|
||||||
return nil, nil //nolint:nilnil
|
return nil, nil //nolint:nilnil
|
||||||
}
|
}
|
||||||
@@ -67,7 +42,7 @@ func envToBoolPtr(envKey string) (boolPtr *bool, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func envToIntPtr(envKey string) (intPtr *int, err error) {
|
func envToIntPtr(envKey string) (intPtr *int, err error) {
|
||||||
s := getCleanedEnv(envKey)
|
s := os.Getenv(envKey)
|
||||||
if s == "" {
|
if s == "" {
|
||||||
return nil, nil //nolint:nilnil
|
return nil, nil //nolint:nilnil
|
||||||
}
|
}
|
||||||
@@ -79,7 +54,7 @@ func envToIntPtr(envKey string) (intPtr *int, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func envToUint8Ptr(envKey string) (uint8Ptr *uint8, err error) {
|
func envToUint8Ptr(envKey string) (uint8Ptr *uint8, err error) {
|
||||||
s := getCleanedEnv(envKey)
|
s := os.Getenv(envKey)
|
||||||
if s == "" {
|
if s == "" {
|
||||||
return nil, nil //nolint:nilnil
|
return nil, nil //nolint:nilnil
|
||||||
}
|
}
|
||||||
@@ -96,7 +71,7 @@ func envToUint8Ptr(envKey string) (uint8Ptr *uint8, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func envToUint16Ptr(envKey string) (uint16Ptr *uint16, err error) {
|
func envToUint16Ptr(envKey string) (uint16Ptr *uint16, err error) {
|
||||||
s := getCleanedEnv(envKey)
|
s := os.Getenv(envKey)
|
||||||
if s == "" {
|
if s == "" {
|
||||||
return nil, nil //nolint:nilnil
|
return nil, nil //nolint:nilnil
|
||||||
}
|
}
|
||||||
@@ -113,7 +88,7 @@ func envToUint16Ptr(envKey string) (uint16Ptr *uint16, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func envToDurationPtr(envKey string) (durationPtr *time.Duration, err error) {
|
func envToDurationPtr(envKey string) (durationPtr *time.Duration, err error) {
|
||||||
s := getCleanedEnv(envKey)
|
s := os.Getenv(envKey)
|
||||||
if s == "" {
|
if s == "" {
|
||||||
return nil, nil //nolint:nilnil
|
return nil, nil //nolint:nilnil
|
||||||
}
|
}
|
||||||
@@ -132,6 +107,17 @@ func lowerAndSplit(csv string) (values []string) {
|
|||||||
return strings.Split(csv, ",")
|
return strings.Split(csv, ",")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var ErrDecodeBase64 = errors.New("cannot decode base64 string")
|
||||||
|
|
||||||
|
func decodeBase64(b64String string) (decoded string, err error) {
|
||||||
|
b, err := base64.StdEncoding.DecodeString(b64String)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("%w: %s: %s",
|
||||||
|
ErrDecodeBase64, b64String, err)
|
||||||
|
}
|
||||||
|
return string(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
func unsetEnvKeys(envKeys []string, err error) (newErr error) {
|
func unsetEnvKeys(envKeys []string, err error) (newErr error) {
|
||||||
newErr = err
|
newErr = err
|
||||||
for _, envKey := range envKeys {
|
for _, envKey := range envKeys {
|
||||||
@@ -144,5 +130,5 @@ func unsetEnvKeys(envKeys []string, err error) (newErr error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func stringPtr(s string) *string { return &s }
|
func stringPtr(s string) *string { return &s }
|
||||||
func uint32Ptr(n uint32) *uint32 { return &n }
|
func uint16Ptr(n uint16) *uint16 { return &n }
|
||||||
func boolPtr(b bool) *bool { return &b }
|
func boolPtr(b bool) *bool { return &b }
|
||||||
|
|||||||
131
internal/configuration/sources/env/httproxy.go
vendored
131
internal/configuration/sources/env/httproxy.go
vendored
@@ -2,6 +2,8 @@ package env
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||||
"github.com/qdm12/govalid/binary"
|
"github.com/qdm12/govalid/binary"
|
||||||
@@ -31,61 +33,138 @@ func (r *Reader) readHTTPProxy() (httpProxy settings.HTTPProxy, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reader) readHTTProxyUser() (user *string) {
|
func (r *Reader) readHTTProxyUser() (user *string) {
|
||||||
_, s := r.getEnvWithRetro("HTTPPROXY_USER", "PROXY_USER", "TINYPROXY_USER")
|
s := os.Getenv("HTTPPROXY_USER")
|
||||||
if s != "" {
|
if s != "" {
|
||||||
return &s
|
return &s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Retro-compatibility
|
||||||
|
s = os.Getenv("TINYPROXY_USER")
|
||||||
|
if s != "" {
|
||||||
|
r.onRetroActive("TINYPROXY_USER", "HTTPPROXY_USER")
|
||||||
|
return &s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retro-compatibility
|
||||||
|
s = os.Getenv("PROXY_USER")
|
||||||
|
if s != "" {
|
||||||
|
r.onRetroActive("PROXY_USER", "HTTPPROXY_USER")
|
||||||
|
return &s
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reader) readHTTProxyPassword() (user *string) {
|
func (r *Reader) readHTTProxyPassword() (user *string) {
|
||||||
_, s := r.getEnvWithRetro("HTTPPROXY_PASSWORD", "PROXY_PASSWORD", "TINYPROXY_PASSWORD")
|
s := os.Getenv("HTTPPROXY_PASSWORD")
|
||||||
if s != "" {
|
if s != "" {
|
||||||
return &s
|
return &s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Retro-compatibility
|
||||||
|
s = os.Getenv("TINYPROXY_PASSWORD")
|
||||||
|
if s != "" {
|
||||||
|
r.onRetroActive("TINYPROXY_PASSWORD", "HTTPPROXY_PASSWORD")
|
||||||
|
return &s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retro-compatibility
|
||||||
|
s = os.Getenv("PROXY_PASSWORD")
|
||||||
|
if s != "" {
|
||||||
|
r.onRetroActive("PROXY_PASSWORD", "HTTPPROXY_PASSWORD")
|
||||||
|
return &s
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reader) readHTTProxyListeningAddress() (listeningAddress string) {
|
func (r *Reader) readHTTProxyListeningAddress() (listeningAddress string) {
|
||||||
key, value := r.getEnvWithRetro("HTTPPROXY_LISTENING_ADDRESS", "PROXY_PORT", "TINYPROXY_PORT", "HTTPPROXY_PORT")
|
// Retro-compatibility
|
||||||
if key == "HTTPPROXY_LISTENING_ADDRESS" {
|
retroKeys := []string{"PROXY_PORT", "TINYPROXY_PORT", "HTTPPROXY_PORT"}
|
||||||
return value
|
for _, retroKey := range retroKeys {
|
||||||
|
s := os.Getenv(retroKey)
|
||||||
|
if s != "" {
|
||||||
|
r.onRetroActive(retroKey, "HTTPPROXY_LISTENING_ADDRESS")
|
||||||
|
return ":" + s
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return ":" + value
|
|
||||||
|
return os.Getenv("HTTPPROXY_LISTENING_ADDRESS")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reader) readHTTProxyEnabled() (enabled *bool, err error) {
|
func (r *Reader) readHTTProxyEnabled() (enabled *bool, err error) {
|
||||||
key, s := r.getEnvWithRetro("HTTPPROXY", "PROXY", "TINYPROXY")
|
s := strings.ToLower(os.Getenv("HTTPPROXY"))
|
||||||
if s == "" {
|
if s != "" {
|
||||||
return nil, nil //nolint:nilnil
|
enabled = new(bool)
|
||||||
|
*enabled, err = binary.Validate(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("environment variable HTTPPROXY: %w", err)
|
||||||
|
}
|
||||||
|
return enabled, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
enabled = new(bool)
|
// Retro-compatibility
|
||||||
*enabled, err = binary.Validate(s)
|
s = strings.ToLower(os.Getenv("TINYPROXY"))
|
||||||
if err != nil {
|
if s != "" {
|
||||||
return nil, fmt.Errorf("environment variable %s: %w", key, err)
|
r.onRetroActive("TINYPROXY", "HTTPPROXY")
|
||||||
|
enabled = new(bool)
|
||||||
|
*enabled, err = binary.Validate(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("environment variable TINYPROXY: %w", err)
|
||||||
|
}
|
||||||
|
return enabled, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return enabled, nil
|
// Retro-compatibility
|
||||||
|
s = strings.ToLower(os.Getenv("PROXY"))
|
||||||
|
if s != "" {
|
||||||
|
r.onRetroActive("PROXY", "HTTPPROXY")
|
||||||
|
enabled = new(bool)
|
||||||
|
*enabled, err = binary.Validate(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("environment variable PROXY: %w", err)
|
||||||
|
}
|
||||||
|
return enabled, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil //nolint:nilnil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reader) readHTTProxyLog() (enabled *bool, err error) {
|
func (r *Reader) readHTTProxyLog() (enabled *bool, err error) {
|
||||||
key, s := r.getEnvWithRetro("HTTPPROXY_LOG", "PROXY_LOG_LEVEL", "TINYPROXY_LOG")
|
s := strings.ToLower(os.Getenv("HTTPPROXY_LOG"))
|
||||||
if s == "" {
|
if s != "" {
|
||||||
return nil, nil //nolint:nilnil
|
enabled = new(bool)
|
||||||
|
*enabled, err = binary.Validate(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("environment variable HTTPPROXY_LOG: %w", err)
|
||||||
|
}
|
||||||
|
return enabled, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var binaryOptions []binary.Option
|
// Retro-compatibility
|
||||||
if key != "HTTPROXY_LOG" {
|
retroOption := binary.OptionEnabled("on", "info", "connect", "notice")
|
||||||
retroOption := binary.OptionEnabled("on", "info", "connect", "notice")
|
s = strings.ToLower(os.Getenv("TINYPROXY_LOG"))
|
||||||
binaryOptions = append(binaryOptions, retroOption)
|
if s != "" {
|
||||||
|
r.onRetroActive("TINYPROXY_LOG", "HTTPPROXY_LOG")
|
||||||
|
enabled = new(bool)
|
||||||
|
*enabled, err = binary.Validate(s, retroOption)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("environment variable TINYPROXY_LOG: %w", err)
|
||||||
|
}
|
||||||
|
return enabled, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
enabled = new(bool)
|
// Retro-compatibility
|
||||||
*enabled, err = binary.Validate(s, binaryOptions...)
|
s = strings.ToLower(os.Getenv("PROXY_LOG_LEVEL"))
|
||||||
if err != nil {
|
if s != "" {
|
||||||
return nil, fmt.Errorf("environment variable %s: %w", key, err)
|
r.onRetroActive("PROXY_LOG_LEVEL", "HTTPPROXY_LOG")
|
||||||
|
enabled = new(bool)
|
||||||
|
*enabled, err = binary.Validate(s, retroOption)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("environment variable PROXY_LOG_LEVEL: %w", err)
|
||||||
|
}
|
||||||
|
return enabled, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return enabled, nil
|
return nil, nil //nolint:nilnil
|
||||||
}
|
}
|
||||||
|
|||||||
21
internal/configuration/sources/env/log.go
vendored
21
internal/configuration/sources/env/log.go
vendored
@@ -3,10 +3,11 @@ package env
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||||
"github.com/qdm12/log"
|
"github.com/qdm12/golibs/logging"
|
||||||
)
|
)
|
||||||
|
|
||||||
func readLog() (log settings.Log, err error) {
|
func readLog() (log settings.Log, err error) {
|
||||||
@@ -18,13 +19,13 @@ func readLog() (log settings.Log, err error) {
|
|||||||
return log, nil
|
return log, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func readLogLevel() (level *log.Level, err error) {
|
func readLogLevel() (level *logging.Level, err error) {
|
||||||
s := getCleanedEnv("LOG_LEVEL")
|
s := os.Getenv("LOG_LEVEL")
|
||||||
if s == "" {
|
if s == "" {
|
||||||
return nil, nil //nolint:nilnil
|
return nil, nil //nolint:nilnil
|
||||||
}
|
}
|
||||||
|
|
||||||
level = new(log.Level)
|
level = new(logging.Level)
|
||||||
*level, err = parseLogLevel(s)
|
*level, err = parseLogLevel(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("environment variable LOG_LEVEL: %w", err)
|
return nil, fmt.Errorf("environment variable LOG_LEVEL: %w", err)
|
||||||
@@ -35,19 +36,19 @@ func readLogLevel() (level *log.Level, err error) {
|
|||||||
|
|
||||||
var ErrLogLevelUnknown = errors.New("log level is unknown")
|
var ErrLogLevelUnknown = errors.New("log level is unknown")
|
||||||
|
|
||||||
func parseLogLevel(s string) (level log.Level, err error) {
|
func parseLogLevel(s string) (level logging.Level, err error) {
|
||||||
switch strings.ToLower(s) {
|
switch strings.ToLower(s) {
|
||||||
case "debug":
|
case "debug":
|
||||||
return log.LevelDebug, nil
|
return logging.LevelDebug, nil
|
||||||
case "info":
|
case "info":
|
||||||
return log.LevelInfo, nil
|
return logging.LevelInfo, nil
|
||||||
case "warning":
|
case "warning":
|
||||||
return log.LevelWarn, nil
|
return logging.LevelWarn, nil
|
||||||
case "error":
|
case "error":
|
||||||
return log.LevelError, nil
|
return logging.LevelError, nil
|
||||||
default:
|
default:
|
||||||
return level, fmt.Errorf(
|
return level, fmt.Errorf(
|
||||||
"%w: %q is not valid and can be one of debug, info, warning or error",
|
"%w: %s: can be one of: debug, info, warning or error",
|
||||||
ErrLogLevelUnknown, s)
|
ErrLogLevelUnknown, s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
111
internal/configuration/sources/env/openvpn.go
vendored
111
internal/configuration/sources/env/openvpn.go
vendored
@@ -2,10 +2,10 @@ package env
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||||
"github.com/qdm12/govalid/binary"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (r *Reader) readOpenVPN() (
|
func (r *Reader) readOpenVPN() (
|
||||||
@@ -14,24 +14,29 @@ func (r *Reader) readOpenVPN() (
|
|||||||
err = unsetEnvKeys([]string{"OPENVPN_CLIENTKEY", "OPENVPN_CLIENTCRT"}, err)
|
err = unsetEnvKeys([]string{"OPENVPN_CLIENTKEY", "OPENVPN_CLIENTCRT"}, err)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
openVPN.Version = getCleanedEnv("OPENVPN_VERSION")
|
openVPN.Version = os.Getenv("OPENVPN_VERSION")
|
||||||
openVPN.User = r.readOpenVPNUser()
|
openVPN.User = r.readOpenVPNUser()
|
||||||
openVPN.Password = r.readOpenVPNPassword()
|
openVPN.Password = r.readOpenVPNPassword()
|
||||||
confFile := getCleanedEnv("OPENVPN_CUSTOM_CONFIG")
|
confFile := os.Getenv("OPENVPN_CUSTOM_CONFIG")
|
||||||
if confFile != "" {
|
if confFile != "" {
|
||||||
openVPN.ConfFile = &confFile
|
openVPN.ConfFile = &confFile
|
||||||
}
|
}
|
||||||
|
|
||||||
ciphersKey, _ := r.getEnvWithRetro("OPENVPN_CIPHERS", "OPENVPN_CIPHER")
|
openVPN.Ciphers = envToCSV("OPENVPN_CIPHER")
|
||||||
openVPN.Ciphers = envToCSV(ciphersKey)
|
auth := os.Getenv("OPENVPN_AUTH")
|
||||||
|
|
||||||
auth := getCleanedEnv("OPENVPN_AUTH")
|
|
||||||
if auth != "" {
|
if auth != "" {
|
||||||
openVPN.Auth = &auth
|
openVPN.Auth = &auth
|
||||||
}
|
}
|
||||||
|
|
||||||
openVPN.ClientCrt = envToStringPtr("OPENVPN_CLIENTCRT")
|
openVPN.ClientCrt, err = readBase64OrNil("OPENVPN_CLIENTCRT")
|
||||||
openVPN.ClientKey = envToStringPtr("OPENVPN_CLIENTKEY")
|
if err != nil {
|
||||||
|
return openVPN, fmt.Errorf("environment variable OPENVPN_CLIENTCRT: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
openVPN.ClientKey, err = readBase64OrNil("OPENVPN_CLIENTKEY")
|
||||||
|
if err != nil {
|
||||||
|
return openVPN, fmt.Errorf("environment variable OPENVPN_CLIENTKEY: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
openVPN.PIAEncPreset = r.readPIAEncryptionPreset()
|
openVPN.PIAEncPreset = r.readPIAEncryptionPreset()
|
||||||
|
|
||||||
@@ -45,64 +50,76 @@ func (r *Reader) readOpenVPN() (
|
|||||||
return openVPN, fmt.Errorf("environment variable OPENVPN_MSSFIX: %w", err)
|
return openVPN, fmt.Errorf("environment variable OPENVPN_MSSFIX: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, openVPN.Interface = r.getEnvWithRetro("VPN_INTERFACE", "OPENVPN_INTERFACE")
|
openVPN.Interface = os.Getenv("OPENVPN_INTERFACE")
|
||||||
|
|
||||||
openVPN.ProcessUser, err = r.readOpenVPNProcessUser()
|
openVPN.Root, err = envToBoolPtr("OPENVPN_ROOT")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return openVPN, err
|
return openVPN, fmt.Errorf("environment variable OPENVPN_ROOT: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO ProcUser once Root is deprecated.
|
||||||
|
|
||||||
openVPN.Verbosity, err = envToIntPtr("OPENVPN_VERBOSITY")
|
openVPN.Verbosity, err = envToIntPtr("OPENVPN_VERBOSITY")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return openVPN, fmt.Errorf("environment variable OPENVPN_VERBOSITY: %w", err)
|
return openVPN, fmt.Errorf("environment variable OPENVPN_VERBOSITY: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
flagsStr := getCleanedEnv("OPENVPN_FLAGS")
|
|
||||||
if flagsStr != "" {
|
|
||||||
openVPN.Flags = strings.Fields(flagsStr)
|
|
||||||
}
|
|
||||||
|
|
||||||
return openVPN, nil
|
return openVPN, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reader) readOpenVPNUser() (user string) {
|
func (r *Reader) readOpenVPNUser() (user string) {
|
||||||
_, user = r.getEnvWithRetro("OPENVPN_USER", "USER")
|
user = os.Getenv("OPENVPN_USER")
|
||||||
|
if user == "" {
|
||||||
|
// Retro-compatibility
|
||||||
|
user = os.Getenv("USER")
|
||||||
|
if user != "" {
|
||||||
|
r.onRetroActive("USER", "OPENVPN_USER")
|
||||||
|
}
|
||||||
|
}
|
||||||
// Remove spaces in user ID to simplify user's life, thanks @JeordyR
|
// Remove spaces in user ID to simplify user's life, thanks @JeordyR
|
||||||
return strings.ReplaceAll(user, " ", "")
|
return strings.ReplaceAll(user, " ", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reader) readOpenVPNPassword() (password string) {
|
func (r *Reader) readOpenVPNPassword() (password string) {
|
||||||
_, password = r.getEnvWithRetro("OPENVPN_PASSWORD", "PASSWORD")
|
password = os.Getenv("OPENVPN_PASSWORD")
|
||||||
return password
|
if password != "" {
|
||||||
}
|
return password
|
||||||
|
|
||||||
func (r *Reader) readPIAEncryptionPreset() (presetPtr *string) {
|
|
||||||
_, preset := r.getEnvWithRetro(
|
|
||||||
"PRIVATE_INTERNET_ACCESS_OPENVPN_ENCRYPTION_PRESET",
|
|
||||||
"PIA_ENCRYPTION", "ENCRYPTION")
|
|
||||||
if preset != "" {
|
|
||||||
return &preset
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Reader) readOpenVPNProcessUser() (processUser string, err error) {
|
|
||||||
key, value := r.getEnvWithRetro("OPENVPN_PROCESS_USER", "OPENVPN_ROOT")
|
|
||||||
if key == "OPENVPN_PROCESS_USER" {
|
|
||||||
return value, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retro-compatibility
|
// Retro-compatibility
|
||||||
if value == "" {
|
password = os.Getenv("PASSWORD")
|
||||||
return "", nil
|
if password != "" {
|
||||||
|
r.onRetroActive("PASSWORD", "OPENVPN_PASSWORD")
|
||||||
}
|
}
|
||||||
root, err := binary.Validate(value)
|
return password
|
||||||
if err != nil {
|
}
|
||||||
return "", fmt.Errorf("environment variable %s: %w", key, err)
|
|
||||||
}
|
func readBase64OrNil(envKey string) (valueOrNil *string, err error) {
|
||||||
if root {
|
value := os.Getenv(envKey)
|
||||||
return "root", nil
|
if value == "" {
|
||||||
}
|
return nil, nil //nolint:nilnil
|
||||||
const defaultNonRootUser = "nonrootuser"
|
}
|
||||||
return defaultNonRootUser, nil
|
|
||||||
|
decoded, err := decodeBase64(value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &decoded, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) readPIAEncryptionPreset() (presetPtr *string) {
|
||||||
|
preset := strings.ToLower(os.Getenv("PIA_ENCRYPTION"))
|
||||||
|
if preset != "" {
|
||||||
|
return &preset
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retro-compatibility
|
||||||
|
preset = strings.ToLower(os.Getenv("ENCRYPTION"))
|
||||||
|
if preset != "" {
|
||||||
|
r.onRetroActive("ENCRYPTION", "PIA_ENCRYPTION")
|
||||||
|
return &preset
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package env
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||||
@@ -12,7 +13,7 @@ import (
|
|||||||
|
|
||||||
func (r *Reader) readOpenVPNSelection() (
|
func (r *Reader) readOpenVPNSelection() (
|
||||||
selection settings.OpenVPNSelection, err error) {
|
selection settings.OpenVPNSelection, err error) {
|
||||||
confFile := getCleanedEnv("OPENVPN_CUSTOM_CONFIG")
|
confFile := os.Getenv("OPENVPN_CUSTOM_CONFIG")
|
||||||
if confFile != "" {
|
if confFile != "" {
|
||||||
selection.ConfFile = &confFile
|
selection.ConfFile = &confFile
|
||||||
}
|
}
|
||||||
@@ -35,9 +36,18 @@ func (r *Reader) readOpenVPNSelection() (
|
|||||||
var ErrOpenVPNProtocolNotValid = errors.New("OpenVPN protocol is not valid")
|
var ErrOpenVPNProtocolNotValid = errors.New("OpenVPN protocol is not valid")
|
||||||
|
|
||||||
func (r *Reader) readOpenVPNProtocol() (tcp *bool, err error) {
|
func (r *Reader) readOpenVPNProtocol() (tcp *bool, err error) {
|
||||||
envKey, protocol := r.getEnvWithRetro("OPENVPN_PROTOCOL", "PROTOCOL")
|
envKey := "OPENVPN_PROTOCOL"
|
||||||
|
protocol := strings.ToLower(os.Getenv("OPENVPN_PROTOCOL"))
|
||||||
|
if protocol == "" {
|
||||||
|
// Retro-compatibility
|
||||||
|
protocol = strings.ToLower(os.Getenv("PROTOCOL"))
|
||||||
|
if protocol != "" {
|
||||||
|
envKey = "PROTOCOL"
|
||||||
|
r.onRetroActive("PROTOCOL", "OPENVPN_PROTOCOL")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
switch strings.ToLower(protocol) {
|
switch protocol {
|
||||||
case "":
|
case "":
|
||||||
return nil, nil //nolint:nilnil
|
return nil, nil //nolint:nilnil
|
||||||
case constants.UDP:
|
case constants.UDP:
|
||||||
@@ -51,9 +61,16 @@ func (r *Reader) readOpenVPNProtocol() (tcp *bool, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reader) readOpenVPNCustomPort() (customPort *uint16, err error) {
|
func (r *Reader) readOpenVPNCustomPort() (customPort *uint16, err error) {
|
||||||
key, s := r.getEnvWithRetro("VPN_ENDPOINT_PORT", "PORT", "OPENVPN_PORT")
|
key := "OPENVPN_PORT"
|
||||||
|
s := os.Getenv(key)
|
||||||
if s == "" {
|
if s == "" {
|
||||||
return nil, nil //nolint:nilnil
|
// Retro-compatibility
|
||||||
|
key = "PORT"
|
||||||
|
s = os.Getenv(key)
|
||||||
|
if s == "" {
|
||||||
|
return nil, nil //nolint:nilnil
|
||||||
|
}
|
||||||
|
r.onRetroActive("PORT", "OPENVPN_PORT")
|
||||||
}
|
}
|
||||||
|
|
||||||
customPort = new(uint16)
|
customPort = new(uint16)
|
||||||
|
|||||||
@@ -6,22 +6,14 @@ import (
|
|||||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (r *Reader) readPortForward() (
|
func readPortForward() (
|
||||||
portForwarding settings.PortForwarding, err error) {
|
portForwarding settings.PortForwarding, err error) {
|
||||||
key, _ := r.getEnvWithRetro(
|
portForwarding.Enabled, err = envToBoolPtr("PORT_FORWARDING")
|
||||||
"PRIVATE_INTERNET_ACCESS_VPN_PORT_FORWARDING",
|
|
||||||
"PORT_FORWARDING")
|
|
||||||
portForwarding.Enabled, err = envToBoolPtr(key)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return portForwarding, fmt.Errorf("environment variable %s: %w", key, err)
|
return portForwarding, fmt.Errorf("environment variable PORT_FORWARDING: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, value := r.getEnvWithRetro(
|
portForwarding.Filepath = envToStringPtr("PORT_FORWARDING_STATUS_FILE")
|
||||||
"PRIVATE_INTERNET_ACCESS_VPN_PORT_FORWARDING_STATUS_FILE",
|
|
||||||
"PORT_FORWARDING_STATUS_FILE")
|
|
||||||
if value != "" {
|
|
||||||
portForwarding.Filepath = stringPtr(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
return portForwarding, nil
|
return portForwarding, nil
|
||||||
}
|
}
|
||||||
|
|||||||
28
internal/configuration/sources/env/pprof.go
vendored
28
internal/configuration/sources/env/pprof.go
vendored
@@ -1,28 +0,0 @@
|
|||||||
package env
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/pprof"
|
|
||||||
)
|
|
||||||
|
|
||||||
func readPprof() (settings pprof.Settings, err error) {
|
|
||||||
settings.Enabled, err = envToBoolPtr("PPROF_ENABLED")
|
|
||||||
if err != nil {
|
|
||||||
return settings, fmt.Errorf("environment variable PPROF_ENABLED: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.BlockProfileRate, err = envToInt("PPROF_BLOCK_PROFILE_RATE")
|
|
||||||
if err != nil {
|
|
||||||
return settings, fmt.Errorf("environment variable PPROF_BLOCK_PROFILE_RATE: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.MutexProfileRate, err = envToInt("PPROF_MUTEX_PROFILE_RATE")
|
|
||||||
if err != nil {
|
|
||||||
return settings, fmt.Errorf("environment variable PPROF_MUTEX_PROFILE_RATE: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.HTTPServer.Address = getCleanedEnv("PPROF_HTTP_SERVER_ADDRESS")
|
|
||||||
|
|
||||||
return settings, nil
|
|
||||||
}
|
|
||||||
25
internal/configuration/sources/env/provider.go
vendored
25
internal/configuration/sources/env/provider.go
vendored
@@ -2,15 +2,15 @@ package env
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
"github.com/qdm12/gluetun/internal/constants/vpn"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (r *Reader) readProvider(vpnType string) (provider settings.Provider, err error) {
|
func (r *Reader) readProvider(vpnType string) (provider settings.Provider, err error) {
|
||||||
provider.Name = r.readVPNServiceProvider(vpnType)
|
provider.Name = readVPNServiceProvider(vpnType)
|
||||||
var providerName string
|
var providerName string
|
||||||
if provider.Name != nil {
|
if provider.Name != nil {
|
||||||
providerName = *provider.Name
|
providerName = *provider.Name
|
||||||
@@ -18,28 +18,27 @@ func (r *Reader) readProvider(vpnType string) (provider settings.Provider, err e
|
|||||||
|
|
||||||
provider.ServerSelection, err = r.readServerSelection(providerName, vpnType)
|
provider.ServerSelection, err = r.readServerSelection(providerName, vpnType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return provider, fmt.Errorf("server selection: %w", err)
|
return provider, fmt.Errorf("cannot read server selection settings: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
provider.PortForwarding, err = r.readPortForward()
|
provider.PortForwarding, err = readPortForward()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return provider, fmt.Errorf("port forwarding: %w", err)
|
return provider, fmt.Errorf("cannot read port forwarding settings: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return provider, nil
|
return provider, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reader) readVPNServiceProvider(vpnType string) (vpnProviderPtr *string) {
|
func readVPNServiceProvider(vpnType string) (vpnProviderPtr *string) {
|
||||||
_, s := r.getEnvWithRetro("VPN_SERVICE_PROVIDER", "VPNSP")
|
s := strings.ToLower(os.Getenv("VPNSP"))
|
||||||
s = strings.ToLower(s)
|
|
||||||
switch {
|
switch {
|
||||||
case vpnType != vpn.Wireguard &&
|
case vpnType != constants.Wireguard &&
|
||||||
getCleanedEnv("OPENVPN_CUSTOM_CONFIG") != "": // retro compatibility
|
os.Getenv("OPENVPN_CUSTOM_CONFIG") != "": // retro compatibility
|
||||||
return stringPtr(providers.Custom)
|
return stringPtr(constants.Custom)
|
||||||
case s == "":
|
case s == "":
|
||||||
return nil
|
return nil
|
||||||
case s == "pia": // retro compatibility
|
case s == "pia": // retro compatibility
|
||||||
return stringPtr(providers.PrivateInternetAccess)
|
return stringPtr(constants.PrivateInternetAccess)
|
||||||
}
|
}
|
||||||
return stringPtr(s)
|
return stringPtr(s)
|
||||||
}
|
}
|
||||||
|
|||||||
13
internal/configuration/sources/env/publicip.go
vendored
13
internal/configuration/sources/env/publicip.go
vendored
@@ -2,6 +2,7 @@ package env
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||||
@@ -19,7 +20,7 @@ func (r *Reader) readPublicIP() (publicIP settings.PublicIP, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func readPublicIPPeriod() (period *time.Duration, err error) {
|
func readPublicIPPeriod() (period *time.Duration, err error) {
|
||||||
s := getCleanedEnv("PUBLICIP_PERIOD")
|
s := os.Getenv("PUBLICIP_PERIOD")
|
||||||
if s == "" {
|
if s == "" {
|
||||||
return nil, nil //nolint:nilnil
|
return nil, nil //nolint:nilnil
|
||||||
}
|
}
|
||||||
@@ -34,9 +35,17 @@ func readPublicIPPeriod() (period *time.Duration, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reader) readPublicIPFilepath() (filepath *string) {
|
func (r *Reader) readPublicIPFilepath() (filepath *string) {
|
||||||
_, s := r.getEnvWithRetro("PUBLICIP_FILE", "IP_STATUS_FILE")
|
s := os.Getenv("PUBLICIP_FILE")
|
||||||
if s != "" {
|
if s != "" {
|
||||||
return &s
|
return &s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Retro-compatibility
|
||||||
|
s = os.Getenv("IP_STATUS_FILE")
|
||||||
|
if s != "" {
|
||||||
|
r.onRetroActive("IP_STATUS_FILE", "PUBLICIP_FILE")
|
||||||
|
return &s
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
30
internal/configuration/sources/env/reader.go
vendored
30
internal/configuration/sources/env/reader.go
vendored
@@ -21,8 +21,6 @@ func New(warner Warner) *Reader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reader) String() string { return "environment variables" }
|
|
||||||
|
|
||||||
func (r *Reader) Read() (settings settings.Settings, err error) {
|
func (r *Reader) Read() (settings settings.Settings, err error) {
|
||||||
settings.VPN, err = r.readVPN()
|
settings.VPN, err = r.readVPN()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -79,12 +77,7 @@ func (r *Reader) Read() (settings settings.Settings, err error) {
|
|||||||
return settings, err
|
return settings, err
|
||||||
}
|
}
|
||||||
|
|
||||||
settings.ControlServer, err = r.readControlServer()
|
settings.ControlServer, err = readControlServer()
|
||||||
if err != nil {
|
|
||||||
return settings, err
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.Pprof, err = readPprof()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return settings, err
|
return settings, err
|
||||||
}
|
}
|
||||||
@@ -97,24 +90,3 @@ func (r *Reader) onRetroActive(oldKey, newKey string) {
|
|||||||
"You are using the old environment variable " + oldKey +
|
"You are using the old environment variable " + oldKey +
|
||||||
", please consider changing it to " + newKey)
|
", please consider changing it to " + newKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
// getEnvWithRetro returns the first environment variable
|
|
||||||
// key and corresponding non empty value from the environment
|
|
||||||
// variable keys given. It first goes through the retroKeys
|
|
||||||
// and end on returning the value corresponding to the currentKey.
|
|
||||||
// Note retroKeys should be in order from oldest to most
|
|
||||||
// recent retro-compatibility key.
|
|
||||||
func (r *Reader) getEnvWithRetro(currentKey string,
|
|
||||||
retroKeys ...string) (key, value string) {
|
|
||||||
// We check retro-compatibility keys first since
|
|
||||||
// the current key might be set in the Dockerfile.
|
|
||||||
for _, key = range retroKeys {
|
|
||||||
value = getCleanedEnv(key)
|
|
||||||
if value != "" {
|
|
||||||
r.onRetroActive(key, currentKey)
|
|
||||||
return key, value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return currentKey, getCleanedEnv(currentKey)
|
|
||||||
}
|
|
||||||
|
|||||||
27
internal/configuration/sources/env/server.go
vendored
27
internal/configuration/sources/env/server.go
vendored
@@ -2,24 +2,29 @@ package env
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||||
"github.com/qdm12/govalid/binary"
|
"github.com/qdm12/govalid/binary"
|
||||||
|
"github.com/qdm12/govalid/port"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (r *Reader) readControlServer() (controlServer settings.ControlServer, err error) {
|
func readControlServer() (controlServer settings.ControlServer, err error) {
|
||||||
controlServer.Log, err = readControlServerLog()
|
controlServer.Log, err = readControlServerLog()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return controlServer, err
|
return controlServer, err
|
||||||
}
|
}
|
||||||
|
|
||||||
controlServer.Address = r.readControlServerAddress()
|
controlServer.Port, err = readControlServerPort()
|
||||||
|
if err != nil {
|
||||||
|
return controlServer, err
|
||||||
|
}
|
||||||
|
|
||||||
return controlServer, nil
|
return controlServer, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func readControlServerLog() (enabled *bool, err error) {
|
func readControlServerLog() (enabled *bool, err error) {
|
||||||
s := getCleanedEnv("HTTP_CONTROL_SERVER_LOG")
|
s := os.Getenv("HTTP_CONTROL_SERVER_LOG")
|
||||||
if s == "" {
|
if s == "" {
|
||||||
return nil, nil //nolint:nilnil
|
return nil, nil //nolint:nilnil
|
||||||
}
|
}
|
||||||
@@ -32,17 +37,17 @@ func readControlServerLog() (enabled *bool, err error) {
|
|||||||
return &log, nil
|
return &log, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reader) readControlServerAddress() (address *string) {
|
func readControlServerPort() (p *uint16, err error) {
|
||||||
key, s := r.getEnvWithRetro("HTTP_CONTROL_SERVER_ADDRESS", "HTTP_CONTROL_SERVER_PORT")
|
s := os.Getenv("HTTP_CONTROL_SERVER_PORT")
|
||||||
if s == "" {
|
if s == "" {
|
||||||
return nil
|
return nil, nil //nolint:nilnil
|
||||||
}
|
}
|
||||||
|
|
||||||
if key == "HTTP_CONTROL_SERVER_ADDRESS" {
|
p = new(uint16)
|
||||||
return &s
|
*p, err = port.Validate(s, port.OptionPortListening(os.Geteuid()))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("environment variable HTTP_CONTROL_SERVER_PORT: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
address = new(string)
|
return p, nil
|
||||||
*address = ":" + s
|
|
||||||
return address
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,11 +4,12 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -19,36 +20,28 @@ func (r *Reader) readServerSelection(vpnProvider, vpnType string) (
|
|||||||
ss settings.ServerSelection, err error) {
|
ss settings.ServerSelection, err error) {
|
||||||
ss.VPN = vpnType
|
ss.VPN = vpnType
|
||||||
|
|
||||||
ss.TargetIP, err = r.readOpenVPNTargetIP()
|
ss.TargetIP, err = readOpenVPNTargetIP()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ss, err
|
return ss, err
|
||||||
}
|
}
|
||||||
|
|
||||||
countriesKey, _ := r.getEnvWithRetro("SERVER_COUNTRIES", "COUNTRY")
|
countriesCSV := os.Getenv("COUNTRY")
|
||||||
ss.Countries = envToCSV(countriesKey)
|
if vpnProvider == constants.Cyberghost && countriesCSV == "" {
|
||||||
if vpnProvider == providers.Cyberghost && len(ss.Countries) == 0 {
|
// Retro-compatibility
|
||||||
// Retro-compatibility for Cyberghost using the REGION variable
|
r.onRetroActive("REGION", "COUNTRY")
|
||||||
ss.Countries = envToCSV("REGION")
|
countriesCSV = os.Getenv("REGION")
|
||||||
if len(ss.Countries) > 0 {
|
}
|
||||||
r.onRetroActive("REGION", "SERVER_COUNTRIES")
|
if countriesCSV != "" {
|
||||||
}
|
ss.Countries = lowerAndSplit(countriesCSV)
|
||||||
}
|
}
|
||||||
|
|
||||||
regionsKey, _ := r.getEnvWithRetro("SERVER_REGIONS", "REGION")
|
ss.Regions = envToCSV("REGION")
|
||||||
ss.Regions = envToCSV(regionsKey)
|
ss.Cities = envToCSV("CITY")
|
||||||
|
|
||||||
citiesKey, _ := r.getEnvWithRetro("SERVER_CITIES", "CITY")
|
|
||||||
ss.Cities = envToCSV(citiesKey)
|
|
||||||
|
|
||||||
ss.ISPs = envToCSV("ISP")
|
ss.ISPs = envToCSV("ISP")
|
||||||
|
ss.Hostnames = envToCSV("SERVER_HOSTNAME")
|
||||||
|
ss.Names = envToCSV("SERVER_NAME")
|
||||||
|
|
||||||
hostnamesKey, _ := r.getEnvWithRetro("SERVER_HOSTNAMES", "SERVER_HOSTNAME")
|
if csv := os.Getenv("SERVER_NUMBER"); csv != "" {
|
||||||
ss.Hostnames = envToCSV(hostnamesKey)
|
|
||||||
|
|
||||||
serverNamesKey, _ := r.getEnvWithRetro("SERVER_NAMES", "SERVER_NAME")
|
|
||||||
ss.Names = envToCSV(serverNamesKey)
|
|
||||||
|
|
||||||
if csv := getCleanedEnv("SERVER_NUMBER"); csv != "" {
|
|
||||||
numbersStrings := strings.Split(csv, ",")
|
numbersStrings := strings.Split(csv, ",")
|
||||||
numbers := make([]uint16, len(numbersStrings))
|
numbers := make([]uint16, len(numbersStrings))
|
||||||
for i, numberString := range numbersStrings {
|
for i, numberString := range numbersStrings {
|
||||||
@@ -66,9 +59,9 @@ func (r *Reader) readServerSelection(vpnProvider, vpnType string) (
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Mullvad only
|
// Mullvad only
|
||||||
ss.OwnedOnly, err = r.readOwnedOnly()
|
ss.OwnedOnly, err = envToBoolPtr("OWNED")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ss, err
|
return ss, fmt.Errorf("environment variable OWNED: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// VPNUnlimited and ProtonVPN only
|
// VPNUnlimited and ProtonVPN only
|
||||||
@@ -106,26 +99,17 @@ var (
|
|||||||
ErrInvalidIP = errors.New("invalid IP address")
|
ErrInvalidIP = errors.New("invalid IP address")
|
||||||
)
|
)
|
||||||
|
|
||||||
func (r *Reader) readOpenVPNTargetIP() (ip net.IP, err error) {
|
func readOpenVPNTargetIP() (ip net.IP, err error) {
|
||||||
envKey, s := r.getEnvWithRetro("VPN_ENDPOINT_IP", "OPENVPN_TARGET_IP")
|
s := os.Getenv("OPENVPN_TARGET_IP")
|
||||||
if s == "" {
|
if s == "" {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
ip = net.ParseIP(s)
|
ip = net.ParseIP(s)
|
||||||
if ip == nil {
|
if ip == nil {
|
||||||
return nil, fmt.Errorf("environment variable %s: %w: %s",
|
return nil, fmt.Errorf("environment variable OPENVPN_TARGET_IP: %w: %s",
|
||||||
envKey, ErrInvalidIP, s)
|
ErrInvalidIP, s)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ip, nil
|
return ip, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reader) readOwnedOnly() (ownedOnly *bool, err error) {
|
|
||||||
envKey, _ := r.getEnvWithRetro("OWNED_ONLY", "OWNED")
|
|
||||||
ownedOnly, err = envToBoolPtr(envKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("environment variable %s: %w", envKey, err)
|
|
||||||
}
|
|
||||||
return ownedOnly, nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package env
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"os"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||||
)
|
)
|
||||||
@@ -25,20 +25,30 @@ func (r *Reader) readShadowsocks() (shadowsocks settings.Shadowsocks, err error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reader) readShadowsocksAddress() (address string) {
|
func (r *Reader) readShadowsocksAddress() (address string) {
|
||||||
key, value := r.getEnvWithRetro("SHADOWSOCKS_LISTENING_ADDRESS", "SHADOWSOCKS_PORT")
|
address = os.Getenv("SHADOWSOCKS_LISTENING_ADDRESS")
|
||||||
if value == "" {
|
if address != "" {
|
||||||
return ""
|
return address
|
||||||
}
|
|
||||||
|
|
||||||
if key == "SHADOWSOCKS_LISTENING_ADDRESS" {
|
|
||||||
return value
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retro-compatibility
|
// Retro-compatibility
|
||||||
return ":" + value
|
portString := os.Getenv("SHADOWSOCKS_PORT")
|
||||||
|
if portString != "" {
|
||||||
|
r.onRetroActive("SHADOWSOCKS_PORT", "SHADOWSOCKS_LISTENING_ADDRESS")
|
||||||
|
return ":" + portString
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reader) readShadowsocksCipher() (cipher string) {
|
func (r *Reader) readShadowsocksCipher() (cipher string) {
|
||||||
_, cipher = r.getEnvWithRetro("SHADOWSOCKS_CIPHER", "SHADOWSOCKS_METHOD")
|
cipher = os.Getenv("SHADOWSOCKS_CIPHER")
|
||||||
return strings.ToLower(cipher)
|
if cipher != "" {
|
||||||
|
return cipher
|
||||||
|
}
|
||||||
|
// Retro-compatibility
|
||||||
|
cipher = os.Getenv("SHADOWSOCKS_METHOD")
|
||||||
|
if cipher != "" {
|
||||||
|
r.onRetroActive("SHADOWSOCKS_METHOD", "SHADOWSOCKS_CIPHER")
|
||||||
|
}
|
||||||
|
return cipher
|
||||||
}
|
}
|
||||||
|
|||||||
34
internal/configuration/sources/env/system.go
vendored
34
internal/configuration/sources/env/system.go
vendored
@@ -3,6 +3,7 @@ package env
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||||
@@ -25,7 +26,7 @@ func (r *Reader) readSystem() (system settings.System, err error) {
|
|||||||
return system, err
|
return system, err
|
||||||
}
|
}
|
||||||
|
|
||||||
system.Timezone = getCleanedEnv("TZ")
|
system.Timezone = os.Getenv("TZ")
|
||||||
|
|
||||||
return system, nil
|
return system, nil
|
||||||
}
|
}
|
||||||
@@ -33,23 +34,30 @@ func (r *Reader) readSystem() (system settings.System, err error) {
|
|||||||
var ErrSystemIDNotValid = errors.New("system ID is not valid")
|
var ErrSystemIDNotValid = errors.New("system ID is not valid")
|
||||||
|
|
||||||
func (r *Reader) readID(key, retroKey string) (
|
func (r *Reader) readID(key, retroKey string) (
|
||||||
id *uint32, err error) {
|
id *uint16, err error) {
|
||||||
idEnvKey, idString := r.getEnvWithRetro(key, retroKey)
|
idEnvKey := key
|
||||||
|
idString := os.Getenv(key)
|
||||||
|
if idString == "" {
|
||||||
|
// retro-compatibility
|
||||||
|
idString = os.Getenv(retroKey)
|
||||||
|
if idString != "" {
|
||||||
|
idEnvKey = retroKey
|
||||||
|
r.onRetroActive(retroKey, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if idString == "" {
|
if idString == "" {
|
||||||
return nil, nil //nolint:nilnil
|
return nil, nil //nolint:nilnil
|
||||||
}
|
}
|
||||||
|
|
||||||
const base = 10
|
idInt, err := strconv.Atoi(idString)
|
||||||
const bitSize = 64
|
|
||||||
const max = uint64(^uint32(0))
|
|
||||||
idUint64, err := strconv.ParseUint(idString, base, bitSize)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("environment variable %s: %w: %s",
|
return nil, fmt.Errorf("environment variable %s: %w: %s: %s",
|
||||||
idEnvKey, ErrSystemIDNotValid, err)
|
idEnvKey, ErrSystemIDNotValid, idString, err)
|
||||||
} else if idUint64 > max {
|
} else if idInt < 0 || idInt > 65535 {
|
||||||
return nil, fmt.Errorf("environment variable %s: %w: %d: must be between 0 and %d",
|
return nil, fmt.Errorf("environment variable %s: %w: %d: must be between 0 and 65535",
|
||||||
idEnvKey, ErrSystemIDNotValid, idUint64, max)
|
idEnvKey, ErrSystemIDNotValid, idInt)
|
||||||
}
|
}
|
||||||
|
|
||||||
return uint32Ptr(uint32(idUint64)), nil
|
return uint16Ptr(uint16(idInt)), nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,51 +14,15 @@ func Test_Reader_readID(t *testing.T) {
|
|||||||
keyValue string
|
keyValue string
|
||||||
retroKeyPrefix string
|
retroKeyPrefix string
|
||||||
retroValue string
|
retroValue string
|
||||||
id *uint32
|
id *uint16
|
||||||
errWrapped error
|
errWrapped error
|
||||||
errMessage string
|
errMessage string
|
||||||
}{
|
}{
|
||||||
"empty string": {
|
|
||||||
keyPrefix: "ID",
|
|
||||||
retroKeyPrefix: "RETRO_ID",
|
|
||||||
},
|
|
||||||
"invalid string": {
|
|
||||||
keyPrefix: "ID",
|
|
||||||
keyValue: "invalid",
|
|
||||||
retroKeyPrefix: "RETRO_ID",
|
|
||||||
errWrapped: ErrSystemIDNotValid,
|
|
||||||
errMessage: `environment variable IDTest_Reader_readID/invalid_string: ` +
|
|
||||||
`system ID is not valid: ` +
|
|
||||||
`strconv.ParseUint: parsing "invalid": invalid syntax`,
|
|
||||||
},
|
|
||||||
"negative number": {
|
|
||||||
keyPrefix: "ID",
|
|
||||||
keyValue: "-1",
|
|
||||||
retroKeyPrefix: "RETRO_ID",
|
|
||||||
errWrapped: ErrSystemIDNotValid,
|
|
||||||
errMessage: `environment variable IDTest_Reader_readID/negative_number: ` +
|
|
||||||
`system ID is not valid: ` +
|
|
||||||
`strconv.ParseUint: parsing "-1": invalid syntax`,
|
|
||||||
},
|
|
||||||
"id 1000": {
|
"id 1000": {
|
||||||
keyPrefix: "ID",
|
keyPrefix: "ID",
|
||||||
keyValue: "1000",
|
keyValue: "1000",
|
||||||
retroKeyPrefix: "RETRO_ID",
|
retroKeyPrefix: "RETRO_ID",
|
||||||
id: uint32Ptr(1000),
|
id: uint16Ptr(1000),
|
||||||
},
|
|
||||||
"max id": {
|
|
||||||
keyPrefix: "ID",
|
|
||||||
keyValue: "4294967295",
|
|
||||||
retroKeyPrefix: "RETRO_ID",
|
|
||||||
id: uint32Ptr(4294967295),
|
|
||||||
},
|
|
||||||
"above max id": {
|
|
||||||
keyPrefix: "ID",
|
|
||||||
keyValue: "4294967296",
|
|
||||||
retroKeyPrefix: "RETRO_ID",
|
|
||||||
errWrapped: ErrSystemIDNotValid,
|
|
||||||
errMessage: `environment variable IDTest_Reader_readID/above_max_id: ` +
|
|
||||||
`system ID is not valid: 4294967296: must be between 0 and 4294967295`,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
20
internal/configuration/sources/env/updater.go
vendored
20
internal/configuration/sources/env/updater.go
vendored
@@ -2,9 +2,12 @@ package env
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||||
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
)
|
)
|
||||||
|
|
||||||
func readUpdater() (updater settings.Updater, err error) {
|
func readUpdater() (updater settings.Updater, err error) {
|
||||||
@@ -18,18 +21,19 @@ func readUpdater() (updater settings.Updater, err error) {
|
|||||||
return updater, err
|
return updater, err
|
||||||
}
|
}
|
||||||
|
|
||||||
updater.MinRatio, err = envToFloat64("UPDATER_MIN_RATIO")
|
// TODO use current provider being used
|
||||||
if err != nil {
|
for _, provider := range constants.AllProviders() {
|
||||||
return updater, fmt.Errorf("environment variable UPDATER_MIN_RATIO: %w", err)
|
if provider == constants.Custom {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
updater.Providers = append(updater.Providers, provider)
|
||||||
}
|
}
|
||||||
|
|
||||||
updater.Providers = envToCSV("UPDATER_VPN_SERVICE_PROVIDERS")
|
|
||||||
|
|
||||||
return updater, nil
|
return updater, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func readUpdaterPeriod() (period *time.Duration, err error) {
|
func readUpdaterPeriod() (period *time.Duration, err error) {
|
||||||
s := getCleanedEnv("UPDATER_PERIOD")
|
s := os.Getenv("UPDATER_PERIOD")
|
||||||
if s == "" {
|
if s == "" {
|
||||||
return nil, nil //nolint:nilnil
|
return nil, nil //nolint:nilnil
|
||||||
}
|
}
|
||||||
@@ -41,11 +45,11 @@ func readUpdaterPeriod() (period *time.Duration, err error) {
|
|||||||
return period, nil
|
return period, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func readUpdaterDNSAddress() (address string, err error) {
|
func readUpdaterDNSAddress() (ip net.IP, err error) {
|
||||||
// TODO this is currently using Cloudflare in
|
// TODO this is currently using Cloudflare in
|
||||||
// plaintext to not be blocked by DNS over TLS by default.
|
// plaintext to not be blocked by DNS over TLS by default.
|
||||||
// If a plaintext address is set in the DNS settings, this one will be used.
|
// If a plaintext address is set in the DNS settings, this one will be used.
|
||||||
// use custom future encrypted DNS written in Go without blocking
|
// use custom future encrypted DNS written in Go without blocking
|
||||||
// as it's too much trouble to start another parallel unbound instance for now.
|
// as it's too much trouble to start another parallel unbound instance for now.
|
||||||
return "", nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package env
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||||
"github.com/qdm12/govalid/binary"
|
"github.com/qdm12/govalid/binary"
|
||||||
@@ -17,7 +18,7 @@ func readVersion() (version settings.Version, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func readVersionEnabled() (enabled *bool, err error) {
|
func readVersionEnabled() (enabled *bool, err error) {
|
||||||
s := getCleanedEnv("VERSION_INFORMATION")
|
s := os.Getenv("VERSION_INFORMATION")
|
||||||
if s == "" {
|
if s == "" {
|
||||||
return nil, nil //nolint:nilnil
|
return nil, nil //nolint:nilnil
|
||||||
}
|
}
|
||||||
|
|||||||
11
internal/configuration/sources/env/vpn.go
vendored
11
internal/configuration/sources/env/vpn.go
vendored
@@ -2,27 +2,28 @@ package env
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (r *Reader) readVPN() (vpn settings.VPN, err error) {
|
func (r *Reader) readVPN() (vpn settings.VPN, err error) {
|
||||||
vpn.Type = strings.ToLower(getCleanedEnv("VPN_TYPE"))
|
vpn.Type = strings.ToLower(os.Getenv("VPN_TYPE"))
|
||||||
|
|
||||||
vpn.Provider, err = r.readProvider(vpn.Type)
|
vpn.Provider, err = r.readProvider(vpn.Type)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return vpn, fmt.Errorf("VPN provider: %w", err)
|
return vpn, fmt.Errorf("cannot read provider settings: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
vpn.OpenVPN, err = r.readOpenVPN()
|
vpn.OpenVPN, err = r.readOpenVPN()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return vpn, fmt.Errorf("OpenVPN: %w", err)
|
return vpn, fmt.Errorf("cannot read OpenVPN settings: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
vpn.Wireguard, err = r.readWireguard()
|
vpn.Wireguard, err = readWireguard()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return vpn, fmt.Errorf("wireguard: %w", err)
|
return vpn, fmt.Errorf("cannot read Wireguard settings: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return vpn, nil
|
return vpn, nil
|
||||||
|
|||||||
13
internal/configuration/sources/env/wireguard.go
vendored
13
internal/configuration/sources/env/wireguard.go
vendored
@@ -3,27 +3,28 @@ package env
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (r *Reader) readWireguard() (wireguard settings.Wireguard, err error) {
|
func readWireguard() (wireguard settings.Wireguard, err error) {
|
||||||
defer func() {
|
defer func() {
|
||||||
err = unsetEnvKeys([]string{"WIREGUARD_PRIVATE_KEY", "WIREGUARD_PRESHARED_KEY"}, err)
|
err = unsetEnvKeys([]string{"WIREGUARD_PRIVATE_KEY", "WIREGUARD_PRESHARED_KEY"}, err)
|
||||||
}()
|
}()
|
||||||
wireguard.PrivateKey = envToStringPtr("WIREGUARD_PRIVATE_KEY")
|
wireguard.PrivateKey = envToStringPtr("WIREGUARD_PRIVATE_KEY")
|
||||||
wireguard.PreSharedKey = envToStringPtr("WIREGUARD_PRESHARED_KEY")
|
wireguard.PreSharedKey = envToStringPtr("WIREGUARD_PRESHARED_KEY")
|
||||||
_, wireguard.Interface = r.getEnvWithRetro("VPN_INTERFACE", "WIREGUARD_INTERFACE")
|
wireguard.Interface = os.Getenv("WIREGUARD_INTERFACE")
|
||||||
wireguard.Addresses, err = r.readWireguardAddresses()
|
wireguard.Addresses, err = readWireguardAddresses()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return wireguard, err // already wrapped
|
return wireguard, err // already wrapped
|
||||||
}
|
}
|
||||||
return wireguard, nil
|
return wireguard, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reader) readWireguardAddresses() (addresses []net.IPNet, err error) {
|
func readWireguardAddresses() (addresses []net.IPNet, err error) {
|
||||||
key, addressesCSV := r.getEnvWithRetro("WIREGUARD_ADDRESSES", "WIREGUARD_ADDRESS")
|
addressesCSV := os.Getenv("WIREGUARD_ADDRESS")
|
||||||
if addressesCSV == "" {
|
if addressesCSV == "" {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
@@ -33,7 +34,7 @@ func (r *Reader) readWireguardAddresses() (addresses []net.IPNet, err error) {
|
|||||||
for i, addressString := range addressStrings {
|
for i, addressString := range addressStrings {
|
||||||
ip, ipNet, err := net.ParseCIDR(addressString)
|
ip, ipNet, err := net.ParseCIDR(addressString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("environment variable %s: %w", key, err)
|
return nil, fmt.Errorf("environment variable WIREGUARD_ADDRESS: %w", err)
|
||||||
}
|
}
|
||||||
ipNet.IP = ip
|
ipNet.IP = ip
|
||||||
addresses[i] = *ipNet
|
addresses[i] = *ipNet
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||||
"github.com/qdm12/govalid/port"
|
"github.com/qdm12/govalid/port"
|
||||||
@@ -11,7 +12,7 @@ import (
|
|||||||
|
|
||||||
func (r *Reader) readWireguardSelection() (
|
func (r *Reader) readWireguardSelection() (
|
||||||
selection settings.WireguardSelection, err error) {
|
selection settings.WireguardSelection, err error) {
|
||||||
selection.EndpointIP, err = r.readWireguardEndpointIP()
|
selection.EndpointIP, err = readWireguardEndpointIP()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return selection, err
|
return selection, err
|
||||||
}
|
}
|
||||||
@@ -21,32 +22,37 @@ func (r *Reader) readWireguardSelection() (
|
|||||||
return selection, err
|
return selection, err
|
||||||
}
|
}
|
||||||
|
|
||||||
selection.PublicKey = getCleanedEnv("WIREGUARD_PUBLIC_KEY")
|
selection.PublicKey = os.Getenv("WIREGUARD_PUBLIC_KEY")
|
||||||
|
|
||||||
return selection, nil
|
return selection, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var ErrIPAddressParse = errors.New("cannot parse IP address")
|
var ErrIPAddressParse = errors.New("cannot parse IP address")
|
||||||
|
|
||||||
func (r *Reader) readWireguardEndpointIP() (endpointIP net.IP, err error) {
|
func readWireguardEndpointIP() (endpointIP net.IP, err error) {
|
||||||
key, s := r.getEnvWithRetro("VPN_ENDPOINT_IP", "WIREGUARD_ENDPOINT_IP")
|
s := os.Getenv("WIREGUARD_ENDPOINT_IP")
|
||||||
if s == "" {
|
if s == "" {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
endpointIP = net.ParseIP(s)
|
endpointIP = net.ParseIP(s)
|
||||||
if endpointIP == nil {
|
if endpointIP == nil {
|
||||||
return nil, fmt.Errorf("environment variable %s: %w: %s",
|
return nil, fmt.Errorf("environment variable WIREGUARD_ENDPOINT_IP: %w: %s",
|
||||||
key, ErrIPAddressParse, s)
|
ErrIPAddressParse, s)
|
||||||
}
|
}
|
||||||
|
|
||||||
return endpointIP, nil
|
return endpointIP, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reader) readWireguardCustomPort() (customPort *uint16, err error) {
|
func (r *Reader) readWireguardCustomPort() (customPort *uint16, err error) {
|
||||||
key, s := r.getEnvWithRetro("VPN_ENDPOINT_PORT", "WIREGUARD_ENDPOINT_PORT")
|
key := "WIREGUARD_ENDPOINT_PORT"
|
||||||
|
s := os.Getenv(key)
|
||||||
if s == "" {
|
if s == "" {
|
||||||
return nil, nil //nolint:nilnil
|
// Retro-compatibility
|
||||||
|
key = "WIREGUARD_PORT"
|
||||||
|
s = os.Getenv(key)
|
||||||
|
if s == "" {
|
||||||
|
return nil, nil //nolint:nilnil
|
||||||
|
}
|
||||||
|
r.onRetroActive("WIREGUARD_PORT", "WIREGUARD_ENDPOINT_PORT")
|
||||||
}
|
}
|
||||||
|
|
||||||
customPort = new(uint16)
|
customPort = new(uint16)
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
package files
|
package files
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/openvpn/extract"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ReadFromFile reads the content of the file as a string.
|
// ReadFromFile reads the content of the file as a string.
|
||||||
@@ -35,21 +32,3 @@ func ReadFromFile(filepath string) (s *string, err error) {
|
|||||||
content = strings.TrimSuffix(content, "\n")
|
content = strings.TrimSuffix(content, "\n")
|
||||||
return &content, nil
|
return &content, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func readPEMFile(filepath string) (base64Ptr *string, err error) {
|
|
||||||
pemData, err := ReadFromFile(filepath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("reading file: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if pemData == nil {
|
|
||||||
return nil, nil //nolint:nilnil
|
|
||||||
}
|
|
||||||
|
|
||||||
base64Data, err := extract.PEM([]byte(*pemData))
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("extracting base64 encoded data from PEM content: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &base64Data, nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -4,24 +4,18 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||||
)
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
|
|
||||||
const (
|
|
||||||
// OpenVPNClientKeyPath is the OpenVPN client key filepath.
|
|
||||||
OpenVPNClientKeyPath = "/gluetun/client.key"
|
|
||||||
// OpenVPNClientCertificatePath is the OpenVPN client certificate filepath.
|
|
||||||
OpenVPNClientCertificatePath = "/gluetun/client.crt"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (r *Reader) readOpenVPN() (settings settings.OpenVPN, err error) {
|
func (r *Reader) readOpenVPN() (settings settings.OpenVPN, err error) {
|
||||||
settings.ClientKey, err = readPEMFile(OpenVPNClientKeyPath)
|
settings.ClientKey, err = ReadFromFile(constants.ClientKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return settings, fmt.Errorf("client key: %w", err)
|
return settings, fmt.Errorf("cannot read client key: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
settings.ClientCrt, err = readPEMFile(OpenVPNClientCertificatePath)
|
settings.ClientCrt, err = ReadFromFile(constants.ClientCertificate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return settings, fmt.Errorf("client certificate: %w", err)
|
return settings, fmt.Errorf("cannot read client certificate: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return settings, nil
|
return settings, nil
|
||||||
|
|||||||
@@ -13,8 +13,6 @@ func New() *Reader {
|
|||||||
return &Reader{}
|
return &Reader{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reader) String() string { return "files" }
|
|
||||||
|
|
||||||
func (r *Reader) Read() (settings settings.Settings, err error) {
|
func (r *Reader) Read() (settings settings.Settings, err error) {
|
||||||
settings.VPN, err = r.readVPN()
|
settings.VPN, err = r.readVPN()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
func (r *Reader) readVPN() (vpn settings.VPN, err error) {
|
func (r *Reader) readVPN() (vpn settings.VPN, err error) {
|
||||||
vpn.OpenVPN, err = r.readOpenVPN()
|
vpn.OpenVPN, err = r.readOpenVPN()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return vpn, fmt.Errorf("OpenVPN: %w", err)
|
return vpn, fmt.Errorf("cannot read OpenVPN settings: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return vpn, nil
|
return vpn, nil
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package mux
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||||
"github.com/qdm12/gluetun/internal/configuration/sources"
|
"github.com/qdm12/gluetun/internal/configuration/sources"
|
||||||
@@ -20,14 +19,6 @@ func New(sources ...sources.Source) *Reader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reader) String() string {
|
|
||||||
sources := make([]string, len(r.sources))
|
|
||||||
for i := range r.sources {
|
|
||||||
sources[i] = r.sources[i].String()
|
|
||||||
}
|
|
||||||
return strings.Join(sources, ", ")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read reads the settings for each source, merging unset fields
|
// Read reads the settings for each source, merging unset fields
|
||||||
// with field set by the next source.
|
// with field set by the next source.
|
||||||
// It then set defaults to remaining unset fields.
|
// It then set defaults to remaining unset fields.
|
||||||
@@ -35,7 +26,7 @@ func (r *Reader) Read() (settings settings.Settings, err error) {
|
|||||||
for _, source := range r.sources {
|
for _, source := range r.sources {
|
||||||
settingsFromSource, err := source.Read()
|
settingsFromSource, err := source.Read()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return settings, fmt.Errorf("reading from %s: %w", source, err)
|
return settings, fmt.Errorf("cannot read from source %T: %w", source, err)
|
||||||
}
|
}
|
||||||
settings.MergeWith(settingsFromSource)
|
settings.MergeWith(settingsFromSource)
|
||||||
}
|
}
|
||||||
@@ -51,7 +42,7 @@ func (r *Reader) ReadHealth() (settings settings.Health, err error) {
|
|||||||
for _, source := range r.sources {
|
for _, source := range r.sources {
|
||||||
settingsFromSource, err := source.ReadHealth()
|
settingsFromSource, err := source.ReadHealth()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return settings, fmt.Errorf("reading from %s: %w", source, err)
|
return settings, fmt.Errorf("cannot read from source %T: %w", source, err)
|
||||||
}
|
}
|
||||||
settings.MergeWith(settingsFromSource)
|
settings.MergeWith(settingsFromSource)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +1,14 @@
|
|||||||
package secrets
|
package secrets
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration/sources/files"
|
"github.com/qdm12/gluetun/internal/configuration/sources/files"
|
||||||
"github.com/qdm12/gluetun/internal/openvpn/extract"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// getCleanedEnv returns an environment variable value with
|
|
||||||
// surrounding spaces and trailing new line characters removed.
|
|
||||||
func getCleanedEnv(envKey string) (value string) {
|
|
||||||
value = os.Getenv(envKey)
|
|
||||||
value = strings.TrimSpace(value)
|
|
||||||
value = strings.TrimSuffix(value, "\r\n")
|
|
||||||
value = strings.TrimSuffix(value, "\n")
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
func readSecretFileAsStringPtr(secretPathEnvKey, defaultSecretPath string) (
|
func readSecretFileAsStringPtr(secretPathEnvKey, defaultSecretPath string) (
|
||||||
stringPtr *string, err error) {
|
stringPtr *string, err error) {
|
||||||
path := getCleanedEnv(secretPathEnvKey)
|
path := os.Getenv(secretPathEnvKey)
|
||||||
if path == "" {
|
if path == "" {
|
||||||
path = defaultSecretPath
|
path = defaultSecretPath
|
||||||
}
|
}
|
||||||
@@ -30,7 +17,7 @@ func readSecretFileAsStringPtr(secretPathEnvKey, defaultSecretPath string) (
|
|||||||
|
|
||||||
func readSecretFileAsString(secretPathEnvKey, defaultSecretPath string) (
|
func readSecretFileAsString(secretPathEnvKey, defaultSecretPath string) (
|
||||||
s string, err error) {
|
s string, err error) {
|
||||||
path := getCleanedEnv(secretPathEnvKey)
|
path := os.Getenv(secretPathEnvKey)
|
||||||
if path == "" {
|
if path == "" {
|
||||||
path = defaultSecretPath
|
path = defaultSecretPath
|
||||||
}
|
}
|
||||||
@@ -42,22 +29,3 @@ func readSecretFileAsString(secretPathEnvKey, defaultSecretPath string) (
|
|||||||
}
|
}
|
||||||
return *stringPtr, nil
|
return *stringPtr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func readPEMSecretFile(secretPathEnvKey, defaultSecretPath string) (
|
|
||||||
base64Ptr *string, err error) {
|
|
||||||
pemData, err := readSecretFileAsStringPtr(secretPathEnvKey, defaultSecretPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("reading secret file: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if pemData == nil {
|
|
||||||
return nil, nil //nolint:nilnil
|
|
||||||
}
|
|
||||||
|
|
||||||
base64Data, err := extract.PEM([]byte(*pemData))
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("extracting base64 encoded data from PEM content: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &base64Data, nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ func readOpenVPN() (
|
|||||||
return settings, fmt.Errorf("cannot read password file: %w", err)
|
return settings, fmt.Errorf("cannot read password file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
settings.ClientKey, err = readPEMSecretFile(
|
settings.ClientKey, err = readSecretFileAsStringPtr(
|
||||||
"OPENVPN_CLIENTKEY_SECRETFILE",
|
"OPENVPN_CLIENTKEY_SECRETFILE",
|
||||||
"/run/secrets/openvpn_clientkey",
|
"/run/secrets/openvpn_clientkey",
|
||||||
)
|
)
|
||||||
@@ -32,7 +32,7 @@ func readOpenVPN() (
|
|||||||
return settings, fmt.Errorf("cannot read client key file: %w", err)
|
return settings, fmt.Errorf("cannot read client key file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
settings.ClientCrt, err = readPEMSecretFile(
|
settings.ClientCrt, err = readSecretFileAsStringPtr(
|
||||||
"OPENVPN_CLIENTCRT_SECRETFILE",
|
"OPENVPN_CLIENTCRT_SECRETFILE",
|
||||||
"/run/secrets/openvpn_clientcrt",
|
"/run/secrets/openvpn_clientcrt",
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -14,8 +14,6 @@ func New() *Reader {
|
|||||||
return &Reader{}
|
return &Reader{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reader) String() string { return "secret files" }
|
|
||||||
|
|
||||||
func (r *Reader) Read() (settings settings.Settings, err error) {
|
func (r *Reader) Read() (settings settings.Settings, err error) {
|
||||||
settings.VPN, err = readVPN()
|
settings.VPN, err = readVPN()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -5,5 +5,4 @@ import "github.com/qdm12/gluetun/internal/configuration/settings"
|
|||||||
type Source interface {
|
type Source interface {
|
||||||
Read() (settings settings.Settings, err error)
|
Read() (settings settings.Settings, err error)
|
||||||
ReadHealth() (settings settings.Health, err error)
|
ReadHealth() (settings settings.Health, err error)
|
||||||
String() string
|
|
||||||
}
|
}
|
||||||
|
|||||||
25
internal/constants/constants.go
Normal file
25
internal/constants/constants.go
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
// Package constants defines constants shared throughout the program.
|
||||||
|
// It also defines constant maps and slices using functions.
|
||||||
|
package constants
|
||||||
|
|
||||||
|
import "sort"
|
||||||
|
|
||||||
|
func makeChoicesUnique(choices []string) []string {
|
||||||
|
uniqueChoices := map[string]struct{}{}
|
||||||
|
for _, choice := range choices {
|
||||||
|
uniqueChoices[choice] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
uniqueChoicesSlice := make([]string, len(uniqueChoices))
|
||||||
|
i := 0
|
||||||
|
for choice := range uniqueChoices {
|
||||||
|
uniqueChoicesSlice[i] = choice
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Slice(uniqueChoicesSlice, func(i, j int) bool {
|
||||||
|
return uniqueChoicesSlice[i] < uniqueChoicesSlice[j]
|
||||||
|
})
|
||||||
|
|
||||||
|
return uniqueChoicesSlice
|
||||||
|
}
|
||||||
26
internal/constants/cyberghost.go
Normal file
26
internal/constants/cyberghost.go
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package constants
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
//nolint:lll
|
||||||
|
const (
|
||||||
|
CyberghostCertificate = "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"
|
||||||
|
)
|
||||||
|
|
||||||
|
func CyberghostCountryChoices(servers []models.CyberghostServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].Country
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
|
|
||||||
|
func CyberghostHostnameChoices(servers []models.CyberghostServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].Hostname
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
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)
|
||||||
|
}
|
||||||
27
internal/constants/fastestvpn.go
Normal file
27
internal/constants/fastestvpn.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package constants
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
//nolint:lll
|
||||||
|
const (
|
||||||
|
FastestvpnCertificate = "MIIFQjCCAyqgAwIBAgIIUfxepT+rr8owDQYJKoZIhvcNAQEMBQAwPzELMAkGA1UEBhMCS1kxEzARBgNVBAoTCkZhc3Rlc3RWUE4xGzAZBgNVBAMTEkZhc3Rlc3RWUE4gUm9vdCBDQTAeFw0xNzA5MTYwMDAxNDZaFw0yNzA5MTQwMDAxNDZaMD8xCzAJBgNVBAYTAktZMRMwEQYDVQQKEwpGYXN0ZXN0VlBOMRswGQYDVQQDExJGYXN0ZXN0VlBOIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC1Xj+WfPTozFynFqc+c3CVrggIllaXEl5bY5VgFynXkqCTM6lSrfC4pNjGXUbqWe6RnGJbM4/6kUn+lQDjFSQV1rzP2eDS8+r5+X2WXh4AoeNRUWhvSG+HiHD/B2EFK+Nd5BRSdUjpKWAtsCmT2bBt7nT0jN1OdeNrLJeyF8siAqv/oQzKznF9aIe/N01b2M8ZOFTzoXi2fZAckgGWui8NB/lzkVIJqSkAPRL8qiJLuRCPVOX1PFD8vV//R8/QumtfbcYBMo6vCk2HmWdrh5OQHPxb3KJtbtG+Z1j8x6HGEAe17djYepBiRMyCEQvYgfD6tvFylc4IquhqE9yaP60PJod5TxpWnRQ6HIGSeBm+S+rYSMalTZ8+pUqOOA+IQCYpfpx6EKIJL/VsW2C7cXdvudxDhXPI5lR/QidCb9Ohq3WkfxXaYwzrngdg2avmNqId9R4KESuM9GoHW0dszfyBCh5wYfeaffMElfDam3B92NUwyhZwtIiv623WVXY9PPz+EDjSJsIAu2Vi1vdJyA4nD4k9Lwmx/1zTc/UaYVLsiBqL2WdfvFTeoWoV+dNxQXSEPhB8gwi8x4O4lZW0cwVy/6fa8KMY8gZbcbSTr7U5bRERfW8l+jY+mYKQ/M/ccgpxaHiw1/+4LWfbJQ7VhJJrTyN0C36FQzY1URkSXg+53wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUmVEL4x6xdCqiqu2OBLs27EA8xGYwDQYJKoZIhvcNAQEMBQADggIBABCpITvO1+R4T9v2+onHiFxU5JjtCZ0zkXqRCMp/Z0UIYbeo1p07pZCPAUjBfGPCkAaR++OiG9sysALdJf8Y6HQKcyuAcWUqQnaIhoZ2JcAP7EKq7uCqsMhcYZD/j3O/3RPtSW5UOx6ItDU+Ua0t9Edho9whNw0VQXmo1JjYoP3FzPjuKoDWTSO1q5eYlZfwcTcs55O2shNkFafPg/6cCm5j6v9nyHrM3sk4LjkrBPUXVx2m/aoz219t8O9Ha9/CdMKXsPO/8gTUzpgnzSgPnGnBmi5xr1nspVN8X4E2f3D+DKqBim3YgslD68NcuFQvJ0/BxZzWVbrr+QXoyzaiCgXuogpIDc2bB6oRXqFnHNz36d4QJmJdWdSaijiS/peQ6EOPgOZ1GuObLWlDCBZLNeQ+N6QaiJxVO4XUj/s22i1IRtwdz84TRHrbWiIpEymsqmb/Ep5r4xV5d6+791axclfOTH7tQrY/SbPtTJI4OEgNekI8YfadQifpelF82MsFFEZuaQn0lj+fvLGtE/zKh3OdLTxRc5TAgBB+0T81+JQosygNr2aFFG0hxar1eyw/gLeG8H+7Ie50pyPvXO4OgB6Key8rSExpilQXlvAT1qX0qS3/K1i/9QkSE9ftIPT6vtwLV2sVQzfyanI4IZgWC6ryhvNLsRn0NFnQclor0+aq"
|
||||||
|
FastestvpnOpenvpnStaticKeyV1 = "697fe793b32cb5091d30f2326d5d124a9412e93d0a44ef7361395d76528fcbfc82c3859dccea70a93cfa8fae409709bff75f844cf5ff0c237f426d0c20969233db0e706edb6bdf195ec3dc11b3f76bc807a77e74662d9a800c8cd1144ebb67b7f0d3f1281d1baf522bfe03b7c3f963b1364fc0769400e413b61ca7b43ab19fac9e0f77e41efd4bda7fd77b1de2d7d7855cbbe3e620cecceac72c21a825b243e651f44d90e290e09c3ad650de8fca99c858bc7caad584bc69b11e5c9fd9381c69c505ec487a65912c672d83ed0113b5a74ddfbd3ab33b3683cec593557520a72c4d6cce46111f56f3396cc3ce7183edce553c68ea0796cf6c4375fad00aaa2a42"
|
||||||
|
)
|
||||||
|
|
||||||
|
func FastestvpnCountriesChoices(servers []models.FastestvpnServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].Country
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
|
|
||||||
|
func FastestvpnHostnameChoices(servers []models.FastestvpnServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].Hostname
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
44
internal/constants/hidemyass.go
Normal file
44
internal/constants/hidemyass.go
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
package constants
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
//nolint:lll
|
||||||
|
const (
|
||||||
|
HideMyAssCA = "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"
|
||||||
|
HideMyAssCertificate = "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"
|
||||||
|
HideMyAssRSAPrivateKey = "MIIEpAIBAAKCAQEA5XY3ERJYWs/YIeBoybivNlu+M32rJs+CAZsh7BnnetTxytI4ngsMRoqXETuis8udp2hsqEHsglLR9tlk9C8yCuKhxbkpdrXFWdISmUq5sa7/wqg/zJF1AZm5Jy0oHNyTHfG6XW61I/h9IN5dmcR9YLir8DVDBNllbtt0z+DnvOhYJOqC30ENahWkTmNKl1cT7EBrR5slddiBJleAb08z77pwsD310e6jWTBySsBcPy+xu/Jj2QgVil/3mstZZDI+noFzs3SkTFBkha/lNTP7NODBQ6m39iaJxz6ZR1xE3v7XU0H5WnpZIcQ2+kmu5Krk2y1GYMKL+9oaotXFPz9v+QIDAQABAoIBAQCcMcssOMOiFWc3MC3EWo4SP4MKQ9n0Uj5Z34LI151FdJyehlj54+VYQ1Cv71tCbjED2sZUBoP69mtsT/EzcsjqtfiOwgrifrs2+BOm+0HKHKiGlcbP9peiHkT10PxEITWXpYtJvGlbcfOjIxqt6B28cBjCK09ShrVQL9ylAKBearRRUacszppntMNTMtN/uG48ZR9Wm+xAczImdG6CrG5sLI/++JwM5PDChLvn5JgMGyOfQZdjNe1oSOVLmqFeG5uu/FS4oMon9+HtfjHJr4ZgA1yQ2wQh3GvEjlP8zwHxEpRJYbxpj6ZbjHZJ2HLX/Gcd9/cXiN8+fQ2zPIYQyG9dAoGBAPUUmt2nJNvl7gj0GbZZ3XR9o+hvj7bJ74W2NhMrw6kjrrzHTAUQd1sBQS8szAQCLqf2ou1aw9AMMBdsLAHydXxvbH7IBAla7rKr23iethtSfjhTNSgQLJHVZlNHfp3hzNtCQZ7j0qVjrteNotrdVF7kKPHDXAK00ICy6SPNjvrXAoGBAO+vdnO15jLeZbbi3lQNS4r8oCadyqyX7ouKE6MtKNhiPsNPGqHKiGcKs/+QylVgYvSmm7TgpsCAiEYeLSPT+Yq3y7HtwVpULlpfAhEJXmvn/6hGpOizx1WNGWhw7nHPWPDzf+jqCGzHdhK0aEZR3MZZQ+U+uKfGiJ8vrvgB7eGvAoGAWxxp5nU48rcsIw/8bxpBhgkfYk33M5EnBqKSv9XJS5wEXhIJZOiWNrLktNEGl4boKXE7aNoRacreJhcE1UR6AOS7hPZ+6atwiePyF4mJUeb9HZtxa493wk9/Vv6BR9il++1Jz/QKX4oLef8hyBP4Rb60qgxirG7kBLR+j9zfhskCgYEAzA5y5xIeuIIU0H4XUDG9dcebxSSjbwsuYIgeLdb9pjMGQhsvjjyyoh8/nT20tLkJpkXN3FFCRjNnUWLRhWYrVkkh1wqWiYOPrwqh5MU4KN/sDWSPcznTY+drkTpMFoKzsvdrl2zf3VR3FneXKv742bkXj601Ykko+XWMHcLutisCgYBSq8IrsjzfaTQiTGI9a7WWsvzK92bq7Abnfq7swAXWcJd/bnjTQKLrrvt2bmwNvlWKAb3c69BFMn0X4t4PuN0iJQ39D6aQAEaM7HwWAmjf5TbodbmgbGxdsUB4xcCIQQ1mvTkigXWrCg0YAD2GZSoaslXAAVv6nR5qWEIa0Hx9GA=="
|
||||||
|
)
|
||||||
|
|
||||||
|
func HideMyAssCountryChoices(servers []models.HideMyAssServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].Country
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
|
|
||||||
|
func HideMyAssRegionChoices(servers []models.HideMyAssServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].Region
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
|
|
||||||
|
func HideMyAssCityChoices(servers []models.HideMyAssServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].City
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
|
|
||||||
|
func HideMyAssHostnameChoices(servers []models.HideMyAssServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].Hostname
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
34
internal/constants/ipvanish.go
Normal file
34
internal/constants/ipvanish.go
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package constants
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
//nolint:lll
|
||||||
|
const (
|
||||||
|
IpvanishCA = "MIIErTCCA5WgAwIBAgIJAMYKzSS8uPKDMA0GCSqGSIb3DQEBDQUAMIGVMQswCQYDVQQGEwJVUzELMAkGA1UECBMCRkwxFDASBgNVBAcTC1dpbnRlciBQYXJrMREwDwYDVQQKEwhJUFZhbmlzaDEVMBMGA1UECxMMSVBWYW5pc2ggVlBOMRQwEgYDVQQDEwtJUFZhbmlzaCBDQTEjMCEGCSqGSIb3DQEJARYUc3VwcG9ydEBpcHZhbmlzaC5jb20wHhcNMTIwMTExMTkzMjIwWhcNMjgxMTAyMTkzMjIwWjCBlTELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkZMMRQwEgYDVQQHEwtXaW50ZXIgUGFyazERMA8GA1UEChMISVBWYW5pc2gxFTATBgNVBAsTDElQVmFuaXNoIFZQTjEUMBIGA1UEAxMLSVBWYW5pc2ggQ0ExIzAhBgkqhkiG9w0BCQEWFHN1cHBvcnRAaXB2YW5pc2guY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAt9DBWNr/IKOuY3TmDP5x7vYZR0DGxLbXU8TyAzBbjUtFFMbhxlHiXVQrZHmgzih94x7BgXM7tWpmMKYVb+gNaqMdWE680Qm3nOwmhy/dulXDkEHAwD05i/iTx4ZaUdtV2vsKBxRg1vdC4AEiwD7bqV4HOi13xcG971aQ55Mj1KeCdA0aNvpat1LWx2jjWxsfI8s2Lv5Fkoi1HO1+vTnnaEsJZrBgAkLXpItqP29Lik3/OBIvkBIxlKrhiVPixE5qNiD+eSPirsmROvsyIonoJtuY4Dw5K6pcNlKyYiwo1IOFYU3YxffwFJk+bSW4WVBhsdf5dGxq/uOHmuz5gdwxCwIDAQABo4H9MIH6MAwGA1UdEwQFMAMBAf8wHQYDVR0OBBYEFEv9FCWJHefBcIPX9p8RHCVOGe6uMIHKBgNVHSMEgcIwgb+AFEv9FCWJHefBcIPX9p8RHCVOGe6uoYGbpIGYMIGVMQswCQYDVQQGEwJVUzELMAkGA1UECBMCRkwxFDASBgNVBAcTC1dpbnRlciBQYXJrMREwDwYDVQQKEwhJUFZhbmlzaDEVMBMGA1UECxMMSVBWYW5pc2ggVlBOMRQwEgYDVQQDEwtJUFZhbmlzaCBDQTEjMCEGCSqGSIb3DQEJARYUc3VwcG9ydEBpcHZhbmlzaC5jb22CCQDGCs0kvLjygzANBgkqhkiG9w0BAQ0FAAOCAQEAI2dkh/43ksV2fdYpVGhYaFZPVqCJoToCez0IvOmLeLGzow+EOSrY508oyjYeNP4VJEjApqo0NrMbKl8g/8bpLBcotOCF1c1HZ+y9v7648uumh01SMjsbBeHOuQcLb+7gX6c0pEmxWv8qj5JiW3/1L1bktnjW5Yp5oFkFSMXjOnIoYKHyKLjN2jtwH6XowUNYpg4qVtKU0CXPdOznWcd9/zSfa393HwJPeeVLbKYaFMC4IEbIUmKYtWyoJ9pJ58smU3pWsHZUg9Zc0LZZNjkNlBdQSLmUHAJ33Bd7pJS0JQeiWviC+4UTmzEWRKa7pDGnYRYNu2cUo0/voStphv8EVA=="
|
||||||
|
)
|
||||||
|
|
||||||
|
func IpvanishCountryChoices(servers []models.IpvanishServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].Country
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
|
|
||||||
|
func IpvanishCityChoices(servers []models.IpvanishServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].City
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
|
|
||||||
|
func IpvanishHostnameChoices(servers []models.IpvanishServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].Hostname
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
43
internal/constants/ivpn.go
Normal file
43
internal/constants/ivpn.go
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
package constants
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
//nolint:lll
|
||||||
|
const (
|
||||||
|
IvpnCA = "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"
|
||||||
|
IvpnOpenvpnStaticKeyV1 = "ac470c93ff9f5602a8aab37dee84a52814d10f20490ad23c47d5d82120c1bf859e93d0696b455d4a1b8d55d40c2685c41ca1d0aef29a3efd27274c4ef09020a3978fe45784b335da6df2d12db97bbb838416515f2a96f04715fd28949c6fe296a925cfada3f8b8928ed7fc963c1563272f5cf46e5e1d9c845d7703ca881497b7e6564a9d1dea9358adffd435295479f47d5298fabf5359613ff5992cb57ff081a04dfb81a26513a6b44a9b5490ad265f8a02384832a59cc3e075ad545461060b7bcab49bac815163cb80983dd51d5b1fd76170ffd904d8291071e96efc3fb777856c717b148d08a510f5687b8a8285dcffe737b98916dd15ef6235dee4266d3b"
|
||||||
|
)
|
||||||
|
|
||||||
|
func IvpnCountryChoices(servers []models.IvpnServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].Country
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
|
|
||||||
|
func IvpnCityChoices(servers []models.IvpnServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].City
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
|
|
||||||
|
func IvpnISPChoices(servers []models.IvpnServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].ISP
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
|
|
||||||
|
func IvpnHostnameChoices(servers []models.IvpnServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].Hostname
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
42
internal/constants/mullvad.go
Normal file
42
internal/constants/mullvad.go
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
package constants
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
//nolint:lll
|
||||||
|
const (
|
||||||
|
MullvadCertificate = "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"
|
||||||
|
)
|
||||||
|
|
||||||
|
func MullvadCountryChoices(servers []models.MullvadServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].Country
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
|
|
||||||
|
func MullvadCityChoices(servers []models.MullvadServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].City
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
|
|
||||||
|
func MullvadHostnameChoices(servers []models.MullvadServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].Hostname
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
|
|
||||||
|
func MullvadISPChoices(servers []models.MullvadServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].ISP
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
27
internal/constants/nordvpn.go
Normal file
27
internal/constants/nordvpn.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package constants
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
//nolint:lll
|
||||||
|
const (
|
||||||
|
NordvpnCertificate = "MIIFCjCCAvKgAwIBAgIBATANBgkqhkiG9w0BAQ0FADA5MQswCQYDVQQGEwJQQTEQMA4GA1UEChMHTm9yZFZQTjEYMBYGA1UEAxMPTm9yZFZQTiBSb290IENBMB4XDTE2MDEwMTAwMDAwMFoXDTM1MTIzMTIzNTk1OVowOTELMAkGA1UEBhMCUEExEDAOBgNVBAoTB05vcmRWUE4xGDAWBgNVBAMTD05vcmRWUE4gUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMkr/BYhyo0F2upsIMXwC6QvkZps3NN2/eQFkfQIS1gql0aejsKsEnmY0Kaon8uZCTXPsRH1gQNgg5D2gixdd1mJUvV3dE3y9FJrXMoDkXdCGBodvKJyU6lcfEVF6/UxHcbBguZK9UtRHS9eJYm3rpL/5huQMCppX7kUeQ8dpCwd3iKITqwd1ZudDqsWaU0vqzC2H55IyaZ/5/TnCk31Q1UP6BksbbuRcwOVskEDsm6YoWDnn/IIzGOYnFJRzQH5jTz3j1QBvRIuQuBuvUkfhx1FEwhwZigrcxXuMP+QgM54kezgziJUaZcOM2zF3lvrwMvXDMfNeIoJABv9ljw969xQ8czQCU5lMVmA37ltv5Ec9U5hZuwk/9QO1Z+d/r6Jx0mlurS8gnCAKJgwa3kyZw6e4FZ8mYL4vpRRhPdvRTWCMJkeB4yBHyhxUmTRgJHm6YR3D6hcFAc9cQcTEl/I60tMdz33G6m0O42sQt/+AR3YCY/RusWVBJB/qNS94EtNtj8iaebCQW1jHAhvGmFILVR9lzD0EzWKHkvyWEjmUVRgCDd6Ne3eFRNS73gdv/C3l5boYySeu4exkEYVxVRn8DhCxs0MnkMHWFK6MyzXCCn+JnWFDYPfDKHvpff/kLDobtPBf+Lbch5wQy9quY27xaj0XwLyjOltpiSTLWae/Q4vAgMBAAGjHTAbMAwGA1UdEwQFMAMBAf8wCwYDVR0PBAQDAgEGMA0GCSqGSIb3DQEBDQUAA4ICAQC9fUL2sZPxIN2mD32VeNySTgZlCEdVmlq471o/bDMP4B8gnQesFRtXY2ZCjs50Jm73B2LViL9qlREmI6vE5IC8IsRBJSV4ce1WYxyXro5rmVg/k6a10rlsbK/eg//GHoJxDdXDOokLUSnxt7gk3QKpX6eCdh67p0PuWm/7WUJQxH2SDxsT9vB/iZriTIEe/ILoOQF0Aqp7AgNCcLcLAmbxXQkXYCCSB35Vp06u+eTWjG0/pyS5V14stGtw+fA0DJp5ZJV4eqJ5LqxMlYvEZ/qKTEdoCeaXv2QEmN6dVqjDoTAok0t5u4YRXzEVCfXAC3ocplNdtCA72wjFJcSbfif4BSC8bDACTXtnPC7nD0VndZLp+RiNLeiENhk0oTC+UVdSc+n2nJOzkCK0vYu0Ads4JGIB7g8IB3z2t9ICmsWrgnhdNdcOe15BincrGA8avQ1cWXsfIKEjbrnEuEk9b5jel6NfHtPKoHc9mDpRdNPISeVawDBM1mJChneHt59Nh8Gah74+TM1jBsw4fhJPvoc7Atcg740JErb904mZfkIEmojCVPhBHVQ9LHBAdM8qFI2kRK0IynOmAZhexlP/aT/kpEsEPyaZQlnBn3An1CRz8h0SPApL8PytggYKeQmRhl499+6jLxcZ2IegLfqq41dzIjwHwTMplg+1pKIOVojpWA=="
|
||||||
|
NordvpnOpenvpnStaticKeyV1 = "e685bdaf659a25a200e2b9e39e51ff030fc72cf1ce07232bd8b2be5e6c670143f51e937e670eee09d4f2ea5a6e4e69965db852c275351b86fc4ca892d78ae002d6f70d029bd79c4d1c26cf14e9588033cf639f8a74809f29f72b9d58f9b8f5fefc7938eade40e9fed6cb92184abb2cc10eb1a296df243b251df0643d53724cdb5a92a1d6cb817804c4a9319b57d53be580815bcfcb2df55018cc83fc43bc7ff82d51f9b88364776ee9d12fc85cc7ea5b9741c4f598c485316db066d52db4540e212e1518a9bd4828219e24b20d88f598a196c9de96012090e333519ae18d35099427e7b372d348d352dc4c85e18cd4b93f8a56ddb2e64eb67adfc9b337157ff4"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NordvpnRegionChoices(servers []models.NordvpnServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].Region
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NordvpnHostnameChoices(servers []models.NordvpnServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].Hostname
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
16
internal/constants/openvpn.go
Normal file
16
internal/constants/openvpn.go
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package constants
|
||||||
|
|
||||||
|
const (
|
||||||
|
AES128cbc = "aes-128-cbc"
|
||||||
|
AES256cbc = "aes-256-cbc"
|
||||||
|
AES128gcm = "aes-128-gcm"
|
||||||
|
AES256gcm = "aes-256-gcm"
|
||||||
|
SHA1 = "sha1"
|
||||||
|
SHA256 = "sha256"
|
||||||
|
SHA512 = "sha512"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
Openvpn24 = "2.4"
|
||||||
|
Openvpn25 = "2.5"
|
||||||
|
)
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
package openvpn
|
|
||||||
|
|
||||||
const (
|
|
||||||
SHA1 = "sha1"
|
|
||||||
SHA256 = "sha256"
|
|
||||||
SHA512 = "sha512"
|
|
||||||
)
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
package openvpn
|
|
||||||
|
|
||||||
const (
|
|
||||||
AES128cbc = "aes-128-cbc"
|
|
||||||
AES256cbc = "aes-256-cbc"
|
|
||||||
AES128gcm = "aes-128-gcm"
|
|
||||||
AES256gcm = "aes-256-gcm"
|
|
||||||
)
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
package openvpn
|
|
||||||
|
|
||||||
const (
|
|
||||||
// AuthConf is the file path to the OpenVPN auth file.
|
|
||||||
AuthConf = "/etc/openvpn/auth.conf"
|
|
||||||
)
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user