Compare commits
371 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
16ecf48b89 | ||
|
|
8fa4fd1b64 | ||
|
|
4db6d1ecf9 | ||
|
|
3b86927ca7 | ||
|
|
8bfa2f9b27 | ||
|
|
fe2a3e4d11 | ||
|
|
b0451d8e50 | ||
|
|
a0b9044fd3 | ||
|
|
c7a841f4b4 | ||
|
|
4ba159e483 | ||
|
|
63a696d7e7 | ||
|
|
d457342b46 | ||
|
|
c246dae2cc | ||
|
|
0f4a2e5224 | ||
|
|
db262050d5 | ||
|
|
227cdea0c8 | ||
|
|
33a6f1c01b | ||
|
|
f6f3c110f0 | ||
|
|
27a3f2c846 | ||
|
|
62169baeea | ||
|
|
4b18636a91 | ||
|
|
51432ca05f | ||
|
|
b5ebdcd040 | ||
|
|
416c1ee113 | ||
|
|
fe97e28461 | ||
|
|
cbd8711a21 | ||
|
|
7578e52ed5 | ||
|
|
0df68f76d5 | ||
|
|
9a528c42f8 | ||
|
|
5607916af6 | ||
|
|
4ad7a2a444 | ||
|
|
ab5dbdca97 | ||
|
|
a97fcda283 | ||
|
|
e955adc1e1 | ||
|
|
ac5141b411 | ||
|
|
f8c189e48a | ||
|
|
2f2a904c64 | ||
|
|
9261dca8ab | ||
|
|
7b5d5c3884 | ||
|
|
7c80d80904 | ||
|
|
ea40b84ec0 | ||
|
|
4e6ef649c4 | ||
|
|
dd40f1d2e6 | ||
|
|
490693bb26 | ||
|
|
c8d33ca5f3 | ||
|
|
e6df026332 | ||
|
|
7a30343053 | ||
|
|
fc02ae9c13 | ||
|
|
f70f0aca9c | ||
|
|
16acd1b162 | ||
|
|
2e3eb1fd7b | ||
|
|
a4cf17f81e | ||
|
|
c0a301611d | ||
|
|
cc934f5c68 | ||
|
|
74426f6202 | ||
|
|
03ed3cb1c8 | ||
|
|
1b1335835b | ||
|
|
5070dbcf7f | ||
|
|
90b9d85742 | ||
|
|
7a3b9941aa | ||
|
|
698095f0a0 | ||
|
|
5a06d8e155 | ||
|
|
7421dcb45f | ||
|
|
554a6cdb92 | ||
|
|
5aa39be973 | ||
|
|
192a7a56a3 | ||
|
|
1d1657e9be | ||
|
|
49b7301295 | ||
|
|
126804c15e | ||
|
|
a7643c6201 | ||
|
|
db2de5fc84 | ||
|
|
5c7b9aa6a1 | ||
|
|
63890c159e | ||
|
|
e7d5ae5dc1 | ||
|
|
b275354a92 | ||
|
|
ac02a64d17 | ||
|
|
9c80150e09 | ||
|
|
31a8bc9062 | ||
|
|
f15dde6502 | ||
|
|
f70609c464 | ||
|
|
c954e6f231 | ||
|
|
cb804577a9 | ||
|
|
e5be20d719 | ||
|
|
875690ab18 | ||
|
|
6a5aa8eddb | ||
|
|
7fdc7de210 | ||
|
|
dd7630997b | ||
|
|
aba5ca4536 | ||
|
|
7506625f40 | ||
|
|
5ddd703f6a | ||
|
|
71c51a7455 | ||
|
|
284d4340b1 | ||
|
|
2c1281d0a2 | ||
|
|
532df9f8d4 | ||
|
|
45b7da1058 | ||
|
|
907daff483 | ||
|
|
7757e8a114 | ||
|
|
e59e28152f | ||
|
|
2fe0594db7 | ||
|
|
794e96b449 | ||
|
|
07282f414c | ||
|
|
e583f9de47 | ||
|
|
8570e09eb9 | ||
|
|
ae5cba519c | ||
|
|
26f3832187 | ||
|
|
5989f29035 | ||
|
|
4ace99f318 | ||
|
|
d1c5e00df8 | ||
|
|
5eacb46226 | ||
|
|
6c17612310 | ||
|
|
fba73a0a0f | ||
|
|
4faef87c03 | ||
|
|
5914cb0e37 | ||
|
|
aa53436e56 | ||
|
|
8dfaebc737 | ||
|
|
062b6a276c | ||
|
|
647cd07de7 | ||
|
|
a530c84c5f | ||
|
|
0bb320065e | ||
|
|
d685d78e74 | ||
|
|
48896176e5 | ||
|
|
54dcf28b31 | ||
|
|
f8bf32bb34 | ||
|
|
748923021c | ||
|
|
a182e3503b | ||
|
|
991cfb8659 | ||
|
|
d0dfc21e2b | ||
|
|
617bd0c600 | ||
|
|
349b5429ba | ||
|
|
8db2944749 | ||
|
|
5986432a22 | ||
|
|
652daec509 | ||
|
|
f94d4b761a | ||
|
|
1ab74e6bb3 | ||
|
|
8e101d49a1 | ||
|
|
7c08e8f607 | ||
|
|
a4caa61c47 | ||
|
|
ebae167815 | ||
|
|
a6f00f2fb2 | ||
|
|
877617cc53 | ||
|
|
2800588ef7 | ||
|
|
f5efa42aaf | ||
|
|
10bd0e1505 | ||
|
|
a4c80b3045 | ||
|
|
dbb71bd695 | ||
|
|
a544f6e604 | ||
|
|
a18e026b70 | ||
|
|
0413a0a1ab | ||
|
|
cb6e9cb761 | ||
|
|
420ae40901 | ||
|
|
34e67f9f99 | ||
|
|
18c53aa597 | ||
|
|
6d2f9b9508 | ||
|
|
6826b05d58 | ||
|
|
9f959dbc6a | ||
|
|
87dbae5745 | ||
|
|
037f19e852 | ||
|
|
62ad8bcd8f | ||
|
|
2805c3388a | ||
|
|
535297dcf5 | ||
|
|
b3b6933ef4 | ||
|
|
edbbcc041a | ||
|
|
d430ebc34f | ||
|
|
0e9abc6e1d | ||
|
|
0c0dd10766 | ||
|
|
75454be6b6 | ||
|
|
4952e3b74e | ||
|
|
04b34a266c | ||
|
|
89b6a031b0 | ||
|
|
d4c6a9bdb5 | ||
|
|
cdc29d48b7 | ||
|
|
f4b464a7cf | ||
|
|
76690d3add | ||
|
|
9898387579 | ||
|
|
1ea15a1a13 | ||
|
|
bda6707685 | ||
|
|
89277828ac | ||
|
|
83b4a3fe55 | ||
|
|
45c9e780c0 | ||
|
|
33b8f5f596 | ||
|
|
447a7c9891 | ||
|
|
1bd355ab96 | ||
|
|
578ef768ab | ||
|
|
0378fe4a7b | ||
|
|
ebd94723c1 | ||
|
|
11b55abff3 | ||
|
|
7f32b43895 | ||
|
|
899f10c35e | ||
|
|
415cb7a945 | ||
|
|
e37f557cd5 | ||
|
|
79f213d97a | ||
|
|
11e1c9f9bb | ||
|
|
3ff3816d77 | ||
|
|
c0bdae8baf | ||
|
|
46e6bd16c9 | ||
|
|
5359257c65 | ||
|
|
5e659dc5b3 | ||
|
|
85e9d7d522 | ||
|
|
b71c8e58f4 | ||
|
|
e998372ce2 | ||
|
|
1216326867 | ||
|
|
f53f0cfffd | ||
|
|
f5f65d534a | ||
|
|
684cef6eab | ||
|
|
b4f6ae030d | ||
|
|
e95c94294f | ||
|
|
36b504609b | ||
|
|
1e6b4ed5eb | ||
|
|
0549326dfb | ||
|
|
87c6ebe1c5 | ||
|
|
f0afac243b | ||
|
|
53472077f4 | ||
|
|
55afdf33e1 | ||
|
|
d3c1f9263c | ||
|
|
6341d1dda6 | ||
|
|
e62e1883c2 | ||
|
|
501b98dbd3 | ||
|
|
029fd1da1f | ||
|
|
fd0267efef | ||
|
|
4414366370 | ||
|
|
08553bc90b | ||
|
|
6f850c4ad4 | ||
|
|
8e1316bd8a | ||
|
|
b345368257 | ||
|
|
90dd3b1b5c | ||
|
|
22455ac76f | ||
|
|
eb18eaf0a9 | ||
|
|
90c6c8485b | ||
|
|
381089ebdf | ||
|
|
292813831d | ||
|
|
991d75a1d0 | ||
|
|
d9dfb81cb4 | ||
|
|
67a9cacb61 | ||
|
|
a91eb95456 | ||
|
|
a295269518 | ||
|
|
42904b6749 | ||
|
|
364f9de756 | ||
|
|
7fd45cf17f | ||
|
|
eb71cfb144 | ||
|
|
48e469917e | ||
|
|
4bcd8ee9f5 | ||
|
|
1b2bcf901a | ||
|
|
306de8feda | ||
|
|
e3696f1eea | ||
|
|
7ff14a356c | ||
|
|
4bde50fb3a | ||
|
|
bd0868d764 | ||
|
|
5ffe8555ba | ||
|
|
78ccbb21cd | ||
|
|
92dbe1ebad | ||
|
|
2eec60cdd2 | ||
|
|
da8c104ebd | ||
|
|
0ef7b66047 | ||
|
|
e32d251cc1 | ||
|
|
9dd5e7bf1d | ||
|
|
b6de6035f6 | ||
|
|
88ccaf0b83 | ||
|
|
52c8bc075f | ||
|
|
2537cd5271 | ||
|
|
db91625de4 | ||
|
|
df78386fbe | ||
|
|
a1d70f740a | ||
|
|
187f42277a | ||
|
|
e1f89bb569 | ||
|
|
1d94f8ab2b | ||
|
|
045ecabb78 | ||
|
|
e6c3cb078a | ||
|
|
afa51b3ff6 | ||
|
|
f9c80b2285 | ||
|
|
fc5cf44b2c | ||
|
|
0c0f1663b1 | ||
|
|
306d8494d6 | ||
|
|
f5c00c3e2d | ||
|
|
ac9571c6b2 | ||
|
|
934fafb64b | ||
|
|
d51514015f | ||
|
|
a9cfd16d53 | ||
|
|
1a6f26fa3b | ||
|
|
0dd723b29f | ||
|
|
7ad6fc8e73 | ||
|
|
31c7e6362b | ||
|
|
072b42d867 | ||
|
|
5d66c193aa | ||
|
|
aa729515b9 | ||
|
|
54b7e23974 | ||
|
|
ad80e0c1ab | ||
|
|
5d7b278957 | ||
|
|
678caaf6a0 | ||
|
|
7228cd7b12 | ||
|
|
7b598a3534 | ||
|
|
9cdc9e9153 | ||
|
|
71ab0416b0 | ||
|
|
10a13bc8a7 | ||
|
|
be386a8e33 | ||
|
|
c33fb8bb97 | ||
|
|
20f20f051b | ||
|
|
179274ade0 | ||
|
|
84607e332b | ||
|
|
8186ef2342 | ||
|
|
19b184adba | ||
|
|
a97fd35d6e | ||
|
|
470ca020e2 | ||
|
|
f64d7c4343 | ||
|
|
c6f68a64e6 | ||
|
|
5aaa122460 | ||
|
|
de169c027f | ||
|
|
314c9663a2 | ||
|
|
21995eb3e3 | ||
|
|
6fc700bd62 | ||
|
|
acdbe2163e | ||
|
|
c3a231e0ab | ||
|
|
984e143336 | ||
|
|
e2ba2f82c0 | ||
|
|
ace5e97e68 | ||
|
|
82d42297e8 | ||
|
|
f99d5e8656 | ||
|
|
0795008c23 | ||
|
|
c975a86a70 | ||
|
|
69eee345d2 | ||
|
|
48afc05bcb | ||
|
|
39a62f5db7 | ||
|
|
006b218ade | ||
|
|
2b09b9c290 | ||
|
|
c42865b3d9 | ||
|
|
836f021a87 | ||
|
|
26b049b361 | ||
|
|
e75627365d | ||
|
|
ae0334c930 | ||
|
|
920ad8b54b | ||
|
|
ac4a4f83fc | ||
|
|
a4652c2d32 | ||
|
|
c40d4e075e | ||
|
|
95967136d3 | ||
|
|
576c1ee0c5 | ||
|
|
5d4032edf4 | ||
|
|
ff3f84f9fd | ||
|
|
2a19b68b9a | ||
|
|
ed6c010aff | ||
|
|
783fb38e41 | ||
|
|
fcab4ae3c6 | ||
|
|
a69c456965 | ||
|
|
0e6db2f1c5 | ||
|
|
7aab18d197 | ||
|
|
d6b39e66d1 | ||
|
|
3f5c72d898 | ||
|
|
691ade794b | ||
|
|
1693c4ed8a | ||
|
|
ae9b3279c3 | ||
|
|
04956e45c7 | ||
|
|
027664af7b | ||
|
|
f8d5f76bdf | ||
|
|
114f9be47f | ||
|
|
c73369e11c | ||
|
|
5603e25542 | ||
|
|
0d8cb66d43 | ||
|
|
e7e4cfca4c | ||
|
|
fd23f1a29b | ||
|
|
57481e3dd7 | ||
|
|
53952b143f | ||
|
|
e7b0f4c6be | ||
|
|
ea143c0c9a | ||
|
|
a951110461 | ||
|
|
7a8f5f53d5 | ||
|
|
1b585159d1 | ||
|
|
f3692cd47f | ||
|
|
15800fd4ff | ||
|
|
9fb085f361 | ||
|
|
1e3f878470 | ||
|
|
bcf9bfa5d3 | ||
|
|
56bdc1f0ae | ||
|
|
9de6428585 |
@@ -1,2 +1,2 @@
|
||||
FROM qmcgaw/godevcontainer
|
||||
RUN apk add wireguard-tools
|
||||
RUN apk add wireguard-tools htop openssl
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"vscode"
|
||||
],
|
||||
"shutdownAction": "stopCompose",
|
||||
"postCreateCommand": "~/.windows.sh && go mod download && go mod tidy",
|
||||
"postCreateCommand": "source ~/.windows.sh && go mod download && go mod tidy",
|
||||
"workspaceFolder": "/workspace",
|
||||
"extensions": [
|
||||
"golang.go",
|
||||
@@ -25,6 +25,7 @@
|
||||
"bajdzis.vscode-database", // Supports connections to mysql or postgres, over SSL, socked
|
||||
"IBM.output-colorizer", // Colorize your output/test logs
|
||||
"mohsen1.prettify-json", // Prettify JSON data
|
||||
"github.copilot",
|
||||
],
|
||||
"settings": {
|
||||
"files.eol": "\n",
|
||||
|
||||
@@ -3,7 +3,6 @@ version: "3.7"
|
||||
services:
|
||||
vscode:
|
||||
build: .
|
||||
image: godevcontainer
|
||||
devices:
|
||||
- /dev/net/tun:/dev/net/tun
|
||||
volumes:
|
||||
@@ -11,16 +10,16 @@ services:
|
||||
# Docker socket to access Docker server
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
# Docker configuration
|
||||
- ~/.docker:/root/.docker:z
|
||||
- ~/.docker:/root/.docker
|
||||
# SSH directory for Linux, OSX and WSL
|
||||
- ~/.ssh:/root/.ssh:z
|
||||
# For Windows without WSL, a copy will be made
|
||||
# from /tmp/.ssh to ~/.ssh to fix permissions
|
||||
#- ~/.ssh:/tmp/.ssh:ro
|
||||
# On Linux and OSX, a symlink /mnt/ssh <-> ~/.ssh is
|
||||
# created in the container. On Windows, files are copied
|
||||
# from /mnt/ssh to ~/.ssh to fix permissions.
|
||||
- ~/.ssh:/mnt/ssh
|
||||
# Shell history persistence
|
||||
- ~/.zsh_history:/root/.zsh_history:z
|
||||
- ~/.zsh_history:/root/.zsh_history
|
||||
# Git config
|
||||
- ~/.gitconfig:/root/.gitconfig:z
|
||||
- ~/.gitconfig:/root/.gitconfig
|
||||
environment:
|
||||
- TZ=
|
||||
cap_add:
|
||||
|
||||
5
.github/ISSUE_TEMPLATE/bug.yml
vendored
5
.github/ISSUE_TEMPLATE/bug.yml
vendored
@@ -40,6 +40,7 @@ body:
|
||||
attributes:
|
||||
label: VPN service provider
|
||||
options:
|
||||
- AirVPN
|
||||
- Custom
|
||||
- Cyberghost
|
||||
- ExpressVPN
|
||||
@@ -54,8 +55,10 @@ body:
|
||||
- PrivateVPN
|
||||
- ProtonVPN
|
||||
- PureVPN
|
||||
- SlickVPN
|
||||
- Surfshark
|
||||
- TorGuard
|
||||
- VPNSecure.me
|
||||
- VPNUnlimited
|
||||
- VyprVPN
|
||||
- WeVPN
|
||||
@@ -96,7 +99,7 @@ body:
|
||||
attributes:
|
||||
label: Share your logs
|
||||
description: No sensitive information is logged out except when running with `LOG_LEVEL=debug`.
|
||||
render: log
|
||||
render: plain text
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
|
||||
39
.github/labels.yml
vendored
39
.github/labels.yml
vendored
@@ -1,18 +1,13 @@
|
||||
- name: "Bug :bug:"
|
||||
color: "b60205"
|
||||
description: ""
|
||||
- name: "Feature request :bulb:"
|
||||
color: "0e8a16"
|
||||
description: ""
|
||||
- name: "Help wanted :pray:"
|
||||
color: "4caf50"
|
||||
description: ""
|
||||
- name: "Documentation :memo:"
|
||||
color: "c5def5"
|
||||
description: ""
|
||||
- name: "Needs more info :thinking:"
|
||||
color: "795548"
|
||||
# Temporary status
|
||||
- name: "🗯️ Waiting for feedback"
|
||||
color: "aadefa"
|
||||
description: ""
|
||||
- name: "🔴 Blocked"
|
||||
color: "ff3f14"
|
||||
description: "Blocked by another issue or pull request"
|
||||
- name: "🔒 After next release"
|
||||
color: "e8f274"
|
||||
description: "Will be done after the next release"
|
||||
|
||||
# Priority
|
||||
- name: "🚨 Urgent"
|
||||
@@ -22,7 +17,18 @@
|
||||
color: "4285f4"
|
||||
description: ""
|
||||
|
||||
# Complexity
|
||||
- name: "☣️ Hard to do"
|
||||
color: "7d0008"
|
||||
description: ""
|
||||
- name: "🟩 Easy to do"
|
||||
color: "34cf43"
|
||||
description: ""
|
||||
|
||||
# VPN providers
|
||||
- name: ":cloud: AirVPN"
|
||||
color: "cfe8d4"
|
||||
description: ""
|
||||
- name: ":cloud: Cyberghost"
|
||||
color: "cfe8d4"
|
||||
description: ""
|
||||
@@ -64,12 +70,17 @@
|
||||
- name: ":cloud: PureVPN"
|
||||
color: "cfe8d4"
|
||||
description: ""
|
||||
- name: ":cloud: SlickVPN"
|
||||
color: "cfe8d4"
|
||||
description: ""
|
||||
- name: ":cloud: Surfshark"
|
||||
color: "cfe8d4"
|
||||
description: ""
|
||||
- name: ":cloud: Torguard"
|
||||
color: "cfe8d4"
|
||||
description: ""
|
||||
- name: ":cloud: VPNSecure.me"
|
||||
color: "cfe8d4"
|
||||
- name: ":cloud: VPNUnlimited"
|
||||
color: "cfe8d4"
|
||||
description: ""
|
||||
|
||||
37
.github/workflows/ci-skip.yml
vendored
Normal file
37
.github/workflows/ci-skip.yml
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
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
|
||||
116
.github/workflows/ci.yml
vendored
116
.github/workflows/ci.yml
vendored
@@ -32,23 +32,27 @@ on:
|
||||
|
||||
jobs:
|
||||
verify:
|
||||
# Only run if it's a push event or if it's a PR from this repository, and it is not dependabot.
|
||||
if: |
|
||||
github.actor != 'dependabot[bot]' &&
|
||||
(github.event_name == 'push' ||
|
||||
github.event_name == 'release' ||
|
||||
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository))
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
env:
|
||||
DOCKER_BUILDKIT: "1"
|
||||
steps:
|
||||
- uses: actions/checkout@v2.4.0
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: reviewdog/action-misspell@v1
|
||||
with:
|
||||
locale: "US"
|
||||
level: error
|
||||
exclude: |
|
||||
./internal/storage/servers.json
|
||||
|
||||
- name: Linting
|
||||
run: docker build --target lint .
|
||||
|
||||
- name: Go mod tidy check
|
||||
run: docker build --target tidy .
|
||||
- name: Mocks check
|
||||
run: docker build --target mocks .
|
||||
|
||||
- name: Build test image
|
||||
run: docker build --target test -t test-container .
|
||||
@@ -60,79 +64,79 @@ jobs:
|
||||
-v "$(pwd)/coverage.txt:/tmp/gobuild/coverage.txt" \
|
||||
test-container
|
||||
|
||||
- name: Code security analysis
|
||||
uses: snyk/actions/golang@master
|
||||
env:
|
||||
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
|
||||
|
||||
- name: Build final image
|
||||
run: docker build -t final-image .
|
||||
|
||||
# - name: Image security analysis
|
||||
# uses: snyk/actions/docker@master
|
||||
# env:
|
||||
# SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
|
||||
# with:
|
||||
# image: final-image
|
||||
codeql:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
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:
|
||||
# Only run if it's a push event or if it's a PR from this repository
|
||||
if: |
|
||||
github.event_name == 'push' ||
|
||||
github.event_name == 'release' ||
|
||||
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository)
|
||||
needs: [verify]
|
||||
github.repository == 'qdm12/gluetun' &&
|
||||
(
|
||||
github.event_name == 'push' ||
|
||||
github.event_name == 'release' ||
|
||||
(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
|
||||
packages: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- 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
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
# extract metadata (tags, labels) for Docker
|
||||
# https://github.com/docker/metadata-action
|
||||
- name: Extract Docker metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v3
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
flavor: |
|
||||
latest=${{ github.ref == 'refs/heads/master' }}
|
||||
latest=${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }}
|
||||
images: |
|
||||
ghcr.io/qdm12/gluetun
|
||||
qmcgaw/gluetun
|
||||
qmcgaw/private-internet-access
|
||||
tags: |
|
||||
type=ref,event=branch,enable=${{ github.ref != 'refs/heads/master' }}
|
||||
type=ref,event=pr
|
||||
type=ref,event=tag,enable=${{ !startsWith(steps.semvercheck.outputs.match, 'true') }}
|
||||
type=semver,pattern=v{{major}}.{{minor}}.{{patch}},enable=${{ startsWith(steps.semvercheck.outputs.match, 'true') }}
|
||||
type=semver,pattern=v{{major}}.{{minor}},enable=${{ startsWith(steps.semvercheck.outputs.match, 'true') }}
|
||||
type=semver,pattern=v{{major}},enable=${{ startsWith(steps.semvercheck.outputs.match, 'true_nonzero') }}
|
||||
type=raw,value=latest,enable=${{ !startsWith(steps.semvercheck.outputs.match, 'true') }}
|
||||
type=semver,pattern=v{{major}}.{{minor}}.{{patch}}
|
||||
type=semver,pattern=v{{major}}.{{minor}}
|
||||
type=semver,pattern=v{{major}},enable=${{ !startsWith(github.ref, 'refs/tags/v0.') }}
|
||||
type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }}
|
||||
|
||||
- uses: docker/setup-qemu-action@v2
|
||||
- uses: docker/setup-buildx-action@v2
|
||||
|
||||
- uses: docker/login-action@v2
|
||||
with:
|
||||
username: qmcgaw
|
||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
|
||||
- uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: qdm12
|
||||
password: ${{ github.token }}
|
||||
|
||||
- name: Short commit
|
||||
id: shortcommit
|
||||
run: echo "::set-output name=value::$(git rev-parse --short HEAD)"
|
||||
|
||||
- name: Build and push final image
|
||||
uses: docker/build-push-action@v2.7.0
|
||||
uses: docker/build-push-action@v4.0.0
|
||||
with:
|
||||
platforms: linux/amd64,linux/386,linux/arm64,linux/arm/v6,linux/arm/v7,linux/ppc64le
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
|
||||
37
.github/workflows/dependabot.yml
vendored
37
.github/workflows/dependabot.yml
vendored
@@ -1,37 +0,0 @@
|
||||
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,18 +1,22 @@
|
||||
name: Docker Hub description
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- README.md
|
||||
- .github/workflows/dockerhub-description.yml
|
||||
jobs:
|
||||
dockerHubDescription:
|
||||
docker-hub-description:
|
||||
if: github.repository == 'qdm12/gluetun'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2.4.0
|
||||
- name: Docker Hub Description
|
||||
uses: peter-evans/dockerhub-description@v2
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: peter-evans/dockerhub-description@v3
|
||||
with:
|
||||
username: qmcgaw
|
||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
|
||||
40
.github/workflows/fork.yml
vendored
40
.github/workflows/fork.yml
vendored
@@ -1,40 +0,0 @@
|
||||
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 .
|
||||
6
.github/workflows/labels.yml
vendored
6
.github/workflows/labels.yml
vendored
@@ -7,9 +7,11 @@ on:
|
||||
- .github/workflows/labels.yml
|
||||
jobs:
|
||||
labeler:
|
||||
permissions:
|
||||
issues: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2.4.0
|
||||
- uses: crazy-max/ghaction-github-labeler@v3
|
||||
- uses: actions/checkout@v3
|
||||
- uses: crazy-max/ghaction-github-labeler@v4
|
||||
with:
|
||||
yaml-file: .github/labels.yml
|
||||
|
||||
15
.github/workflows/misspell.yml
vendored
15
.github/workflows/misspell.yml
vendored
@@ -1,15 +0,0 @@
|
||||
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,48 +7,45 @@ issues:
|
||||
- path: _test\.go
|
||||
linters:
|
||||
- dupl
|
||||
- maligned
|
||||
- goerr113
|
||||
- path: internal/server/
|
||||
- containedctx
|
||||
- path: "internal\\/server\\/.+\\.go"
|
||||
linters:
|
||||
- dupl
|
||||
- path: internal/configuration/
|
||||
- path: "internal\\/configuration\\/settings\\/.+\\.go"
|
||||
linters:
|
||||
- dupl
|
||||
- path: internal/constants/
|
||||
linters:
|
||||
- dupl
|
||||
- text: "exported: exported var Err*"
|
||||
linters:
|
||||
- revive
|
||||
- text: "mnd: Magic number: 0644*"
|
||||
- text: "^mnd: Magic number: 0[0-9]{3}, in <argument> detected$"
|
||||
source: "^.+= os\\.OpenFile\\(.+, .+, 0[0-9]{3}\\)"
|
||||
linters:
|
||||
- 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:
|
||||
- gomnd
|
||||
- text: "variable 'mssFix' is only used in the if-statement*"
|
||||
path: "openvpnconf.go"
|
||||
- linters:
|
||||
- lll
|
||||
source: "^//go:generate .+$"
|
||||
- text: "returns interface \\(github\\.com\\/vishvananda\\/netlink\\.Link\\)"
|
||||
linters:
|
||||
- ifshort
|
||||
- text: "variable 'auth' is only used in the if-statement*"
|
||||
path: "openvpnconf.go"
|
||||
linters:
|
||||
- ifshort
|
||||
- ireturn
|
||||
|
||||
linters:
|
||||
enable:
|
||||
# - cyclop
|
||||
# - errorlint
|
||||
# - ireturn
|
||||
# - varnamelen
|
||||
# - wrapcheck
|
||||
- asasalint
|
||||
- asciicheck
|
||||
- bidichk
|
||||
- bodyclose
|
||||
- containedctx
|
||||
- decorder
|
||||
- dogsled
|
||||
- dupl
|
||||
- durationcheck
|
||||
- errchkjson
|
||||
- errname
|
||||
- execinquery
|
||||
- exhaustive
|
||||
- exportloopref
|
||||
- forcetypeassert
|
||||
@@ -67,9 +64,12 @@ linters:
|
||||
- gomoddirectives
|
||||
- goprintffuncname
|
||||
- gosec
|
||||
- ifshort
|
||||
- grouper
|
||||
- importas
|
||||
- interfacebloat
|
||||
- ireturn
|
||||
- lll
|
||||
- maintidx
|
||||
- makezero
|
||||
- misspell
|
||||
- nakedret
|
||||
@@ -78,10 +78,11 @@ linters:
|
||||
- nilnil
|
||||
- noctx
|
||||
- nolintlint
|
||||
- nosprintfhostport
|
||||
- prealloc
|
||||
- predeclared
|
||||
- predeclared
|
||||
- promlinter
|
||||
- reassign
|
||||
- revive
|
||||
- rowserrcheck
|
||||
- sqlclosecheck
|
||||
@@ -90,6 +91,7 @@ linters:
|
||||
- tparallel
|
||||
- unconvert
|
||||
- unparam
|
||||
- usestdlibvars
|
||||
- wastedassign
|
||||
- whitespace
|
||||
|
||||
|
||||
35
.vscode/launch.json
vendored
Normal file
35
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Update a VPN provider servers data",
|
||||
"type": "go",
|
||||
"request": "launch",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"program": "cmd/gluetun/main.go",
|
||||
"args": [
|
||||
"update",
|
||||
"${input:updateMode}",
|
||||
"-providers",
|
||||
"${input:provider}"
|
||||
],
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"id": "provider",
|
||||
"type": "promptString",
|
||||
"description": "Please enter a provider (or comma separated list of providers)",
|
||||
},
|
||||
{
|
||||
"id": "updateMode",
|
||||
"type": "pickString",
|
||||
"description": "Update mode to use",
|
||||
"options": [
|
||||
"-maintainer",
|
||||
"-enduser"
|
||||
],
|
||||
"default": "-maintainer"
|
||||
},
|
||||
]
|
||||
}
|
||||
94
Dockerfile
94
Dockerfile
@@ -1,18 +1,22 @@
|
||||
ARG ALPINE_VERSION=3.15
|
||||
ARG GO_ALPINE_VERSION=3.15
|
||||
ARG GO_VERSION=1.17
|
||||
ARG ALPINE_VERSION=3.17
|
||||
ARG GO_ALPINE_VERSION=3.17
|
||||
ARG GO_VERSION=1.20
|
||||
ARG XCPUTRANSLATE_VERSION=v0.6.0
|
||||
ARG GOLANGCI_LINT_VERSION=v1.43.0
|
||||
ARG GOLANGCI_LINT_VERSION=v1.51.2
|
||||
ARG MOCKGEN_VERSION=v1.6.0
|
||||
ARG BUILDPLATFORM=linux/amd64
|
||||
|
||||
FROM --platform=${BUILDPLATFORM} qmcgaw/xcputranslate:${XCPUTRANSLATE_VERSION} AS xcputranslate
|
||||
FROM --platform=${BUILDPLATFORM} qmcgaw/binpot:golangci-lint-${GOLANGCI_LINT_VERSION} AS golangci-lint
|
||||
FROM --platform=${BUILDPLATFORM} qmcgaw/binpot:mockgen-${MOCKGEN_VERSION} AS mockgen
|
||||
|
||||
FROM --platform=${BUILDPLATFORM} golang:${GO_VERSION}-alpine${GO_ALPINE_VERSION} AS base
|
||||
COPY --from=xcputranslate /xcputranslate /usr/local/bin/xcputranslate
|
||||
RUN apk --update add git g++
|
||||
# Note: findutils needed to have xargs support `-d` flag for mocks stage.
|
||||
RUN apk --update add git g++ findutils
|
||||
ENV CGO_ENABLED=0
|
||||
COPY --from=golangci-lint /bin /go/bin/golangci-lint
|
||||
COPY --from=mockgen /bin /go/bin/mockgen
|
||||
WORKDIR /tmp/gobuild
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
@@ -30,14 +34,17 @@ FROM --platform=${BUILDPLATFORM} base AS lint
|
||||
COPY .golangci.yml ./
|
||||
RUN golangci-lint run --timeout=10m
|
||||
|
||||
FROM --platform=${BUILDPLATFORM} base AS tidy
|
||||
FROM --platform=${BUILDPLATFORM} base AS mocks
|
||||
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
|
||||
git config core.fileMode false && \
|
||||
git add -A && \
|
||||
git commit -m "snapshot" && \
|
||||
grep -lr -E '^// Code generated by MockGen\. DO NOT EDIT\.$' . | xargs -r -d '\n' rm && \
|
||||
go generate -run "mockgen" ./... && \
|
||||
git diff --exit-code && \
|
||||
rm -rf .git/
|
||||
|
||||
FROM --platform=${BUILDPLATFORM} base AS build
|
||||
ARG TARGETPLATFORM
|
||||
@@ -66,8 +73,12 @@ LABEL \
|
||||
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.description="VPN swiss-knife like client to tunnel to multiple VPN servers using OpenVPN, IPtables, DNS over TLS, Shadowsocks, an HTTP proxy and Alpine Linux"
|
||||
ENV VPNSP=pia \
|
||||
ENV VPN_SERVICE_PROVIDER=pia \
|
||||
VPN_TYPE=openvpn \
|
||||
# Common VPN options
|
||||
VPN_ENDPOINT_IP= \
|
||||
VPN_ENDPOINT_PORT= \
|
||||
VPN_INTERFACE=tun0 \
|
||||
# OpenVPN
|
||||
OPENVPN_PROTOCOL=udp \
|
||||
OPENVPN_USER= \
|
||||
@@ -77,45 +88,48 @@ ENV VPNSP=pia \
|
||||
OPENVPN_VERSION=2.5 \
|
||||
OPENVPN_VERBOSITY=1 \
|
||||
OPENVPN_FLAGS= \
|
||||
OPENVPN_CIPHER= \
|
||||
OPENVPN_CIPHERS= \
|
||||
OPENVPN_AUTH= \
|
||||
OPENVPN_ROOT=yes \
|
||||
OPENVPN_TARGET_IP= \
|
||||
OPENVPN_IPV6=off \
|
||||
OPENVPN_PROCESS_USER= \
|
||||
OPENVPN_CUSTOM_CONFIG= \
|
||||
OPENVPN_INTERFACE=tun0 \
|
||||
OPENVPN_PORT= \
|
||||
# Wireguard
|
||||
WIREGUARD_PRIVATE_KEY= \
|
||||
WIREGUARD_PRESHARED_KEY= \
|
||||
WIREGUARD_PUBLIC_KEY= \
|
||||
WIREGUARD_ADDRESS= \
|
||||
WIREGUARD_ENDPOINT_IP= \
|
||||
WIREGUARD_ENDPOINT_PORT= \
|
||||
WIREGUARD_INTERFACE=wg0 \
|
||||
WIREGUARD_ADDRESSES= \
|
||||
WIREGUARD_IMPLEMENTATION=auto \
|
||||
# VPN server filtering
|
||||
REGION= \
|
||||
COUNTRY= \
|
||||
CITY= \
|
||||
SERVER_HOSTNAME= \
|
||||
SERVER_REGIONS= \
|
||||
SERVER_COUNTRIES= \
|
||||
SERVER_CITIES= \
|
||||
SERVER_HOSTNAMES= \
|
||||
# # Mullvad only:
|
||||
ISP= \
|
||||
OWNED=no \
|
||||
OWNED_ONLY=no \
|
||||
# # Private Internet Access only:
|
||||
PIA_ENCRYPTION= \
|
||||
PORT_FORWARDING=off \
|
||||
PORT_FORWARDING_STATUS_FILE="/tmp/gluetun/forwarded_port" \
|
||||
PRIVATE_INTERNET_ACCESS_OPENVPN_ENCRYPTION_PRESET= \
|
||||
PRIVATE_INTERNET_ACCESS_VPN_PORT_FORWARDING=off \
|
||||
PRIVATE_INTERNET_ACCESS_VPN_PORT_FORWARDING_STATUS_FILE="/tmp/gluetun/forwarded_port" \
|
||||
# # Cyberghost only:
|
||||
OPENVPN_CERT= \
|
||||
OPENVPN_KEY= \
|
||||
OPENVPN_CLIENTCRT_SECRETFILE=/run/secrets/openvpn_clientcrt \
|
||||
OPENVPN_CLIENTKEY_SECRETFILE=/run/secrets/openvpn_clientkey \
|
||||
# # VPNSecure only:
|
||||
OPENVPN_ENCRYPTED_KEY= \
|
||||
OPENVPN_ENCRYPTED_KEY_SECRETFILE=/run/secrets/openvpn_encrypted_key \
|
||||
OPENVPN_KEY_PASSPHRASE= \
|
||||
OPENVPN_KEY_PASSPHRASE_SECRETFILE=/run/secrets/openvpn_key_passphrase \
|
||||
# # Nordvpn only:
|
||||
SERVER_NUMBER= \
|
||||
# # PIA and ProtonVPN only:
|
||||
SERVER_NAME= \
|
||||
# # PIA only:
|
||||
SERVER_NAMES= \
|
||||
# # ProtonVPN only:
|
||||
FREE_ONLY= \
|
||||
# # Surfshark only:
|
||||
MULTIHOP_ONLY= \
|
||||
# # VPN Secure only:
|
||||
PREMIUM_ONLY= \
|
||||
# Firewall
|
||||
FIREWALL=on \
|
||||
FIREWALL_VPN_INPUT_PORTS= \
|
||||
@@ -126,7 +140,7 @@ ENV VPNSP=pia \
|
||||
LOG_LEVEL=info \
|
||||
# Health
|
||||
HEALTH_SERVER_ADDRESS=127.0.0.1:9999 \
|
||||
HEALTH_TARGET_ADDRESS=github.com:443 \
|
||||
HEALTH_TARGET_ADDRESS=cloudflare.com:443 \
|
||||
HEALTH_VPN_DURATION_INITIAL=6s \
|
||||
HEALTH_VPN_DURATION_ADDITION=5s \
|
||||
# DNS over TLS
|
||||
@@ -143,7 +157,7 @@ ENV VPNSP=pia \
|
||||
BLOCK_ADS=off \
|
||||
UNBLOCK= \
|
||||
DNS_UPDATE_PERIOD=24h \
|
||||
DNS_PLAINTEXT_ADDRESS=127.0.0.1 \
|
||||
DNS_ADDRESS=127.0.0.1 \
|
||||
DNS_KEEP_NAMESERVER=off \
|
||||
# HTTP proxy
|
||||
HTTPPROXY= \
|
||||
@@ -160,11 +174,20 @@ ENV VPNSP=pia \
|
||||
SHADOWSOCKS_PASSWORD= \
|
||||
SHADOWSOCKS_PASSWORD_SECRETFILE=/run/secrets/shadowsocks_password \
|
||||
SHADOWSOCKS_CIPHER=chacha20-ietf-poly1305 \
|
||||
# Control server
|
||||
HTTP_CONTROL_SERVER_ADDRESS=":8000" \
|
||||
# Server data updater
|
||||
UPDATER_PERIOD=0 \
|
||||
UPDATER_MIN_RATIO=0.8 \
|
||||
UPDATER_VPN_SERVICE_PROVIDERS= \
|
||||
# Public IP
|
||||
PUBLICIP_FILE="/tmp/gluetun/ip" \
|
||||
PUBLICIP_PERIOD=12h \
|
||||
# Pprof
|
||||
PPROF_ENABLED=no \
|
||||
PPROF_BLOCK_PROFILE_RATE=0 \
|
||||
PPROF_MUTEX_PROFILE_RATE=0 \
|
||||
PPROF_HTTP_SERVER_ADDRESS=":6060" \
|
||||
# Extras
|
||||
VERSION_INFORMATION=on \
|
||||
TZ= \
|
||||
@@ -174,8 +197,9 @@ ENTRYPOINT ["/gluetun-entrypoint"]
|
||||
EXPOSE 8000/tcp 8888/tcp 8388/tcp 8388/udp
|
||||
HEALTHCHECK --interval=5s --timeout=5s --start-period=10s --retries=1 CMD /gluetun-entrypoint healthcheck
|
||||
ARG TARGETPLATFORM
|
||||
RUN apk add --no-cache --update -l apk-tools && \
|
||||
apk add --no-cache --update -X "https://dl-cdn.alpinelinux.org/alpine/v3.12/main" openvpn==2.4.11-r0 && \
|
||||
RUN apk add --no-cache --update -l wget && \
|
||||
apk add --no-cache --update -X "https://dl-cdn.alpinelinux.org/alpine/v3.12/main" openvpn==2.4.12-r0 && \
|
||||
apk add --no-cache --update -X "https://dl-cdn.alpinelinux.org/alpine/v3.16/main" openssl\~1.1 && \
|
||||
mv /usr/sbin/openvpn /usr/sbin/openvpn2.4 && \
|
||||
apk del openvpn && \
|
||||
apk add --no-cache --update openvpn ca-certificates iptables ip6tables unbound tzdata && \
|
||||
|
||||
257
README.md
257
README.md
@@ -1,127 +1,130 @@
|
||||
# Gluetun VPN client
|
||||
|
||||
*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!
|
||||
|
||||

|
||||
|
||||
[](https://github.com/qdm12/gluetun/actions/workflows/ci.yml)
|
||||
|
||||
[](https://hub.docker.com/r/qmcgaw/gluetun)
|
||||
[](https://hub.docker.com/r/qmcgaw/gluetun)
|
||||
|
||||
[](https://hub.docker.com/r/qmcgaw/gluetun)
|
||||
[](https://hub.docker.com/r/qmcgaw/gluetun)
|
||||
|
||||

|
||||

|
||||
[](https://hub.docker.com/r/qmcgaw/gluetun/tags?page=1&ordering=last_updated)
|
||||

|
||||

|
||||
|
||||
[](https://hub.docker.com/r/qmcgaw/gluetun/tags)
|
||||
|
||||
[](https://github.com/qdm12/gluetun/commits/master)
|
||||
[](https://github.com/qdm12/gluetun/graphs/contributors)
|
||||
[](https://github.com/qdm12/gluetun/pulls?q=is%3Apr+is%3Aclosed)
|
||||
[](https://github.com/qdm12/gluetun/issues)
|
||||
[](https://github.com/qdm12/gluetun/issues?q=is%3Aissue+is%3Aclosed)
|
||||
|
||||
[](https://github.com/qdm12/gluetun)
|
||||

|
||||

|
||||

|
||||
|
||||

|
||||
|
||||
## Quick links
|
||||
|
||||
- [Setup](#Setup)
|
||||
- [Features](#Features)
|
||||
- Problem?
|
||||
- [Check the Wiki](https://github.com/qdm12/gluetun/wiki)
|
||||
- [Start a discussion](https://github.com/qdm12/gluetun/discussions)
|
||||
- [Fix the Unraid template](https://github.com/qdm12/gluetun/discussions/550)
|
||||
- Suggestion?
|
||||
- [Create an issue](https://github.com/qdm12/gluetun/issues)
|
||||
- [Join the Slack channel](https://join.slack.com/t/qdm12/shared_invite/enQtOTE0NjcxNTM1ODc5LTYyZmVlOTM3MGI4ZWU0YmJkMjUxNmQ4ODQ2OTAwYzMxMTlhY2Q1MWQyOWUyNjc2ODliNjFjMDUxNWNmNzk5MDk)
|
||||
- Happy?
|
||||
- Sponsor me on [github.com/sponsors/qdm12](https://github.com/sponsors/qdm12)
|
||||
- Donate to [paypal.me/qmcgaw](https://www.paypal.me/qmcgaw)
|
||||
- Drop me [an email](mailto:quentin.mcgaw@gmail.com)
|
||||
- Video:
|
||||
|
||||
[](https://youtu.be/0F6I03LQcI4)
|
||||
|
||||
- [Substack Console interview](https://console.substack.com/p/console-72)
|
||||
|
||||
## Features
|
||||
|
||||
- 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 OpenVPN for all providers listed
|
||||
- Supports Wireguard both kernelspace and userspace
|
||||
- For **Mullvad**, **Ivpn** and **Windscribe**
|
||||
- For **Torguard**, **VPN Unlimited** and **WeVPN** using [the custom provider](https://github.com/qdm12/gluetun/wiki/Custom-provider)
|
||||
- For custom Wireguard configurations using [the custom provider](https://github.com/qdm12/gluetun/wiki/Custom-provider)
|
||||
- More in progress, see [#134](https://github.com/qdm12/gluetun/issues/134)
|
||||
- DNS over TLS baked in with service provider(s) of your choice
|
||||
- DNS fine blocking of malicious/ads/surveillance hostnames and IP addresses, with live update every 24 hours
|
||||
- Choose the vpn network protocol, `udp` or `tcp`
|
||||
- Built in firewall kill switch to allow traffic only with needed the VPN servers and LAN devices
|
||||
- Built in Shadowsocks proxy (protocol based on SOCKS5 with an encryption layer, tunnels TCP+UDP)
|
||||
- Built in HTTP proxy (tunnels HTTP and HTTPS through TCP)
|
||||
- [Connect other containers to it](https://github.com/qdm12/gluetun/wiki/Connect-a-container-to-gluetun)
|
||||
- [Connect LAN devices to it](https://github.com/qdm12/gluetun/wiki/Connect-a-LAN-device-to-gluetun)
|
||||
- Compatible with amd64, i686 (32 bit), **ARM** 64 bit, ARM 32 bit v6 and v7, and even ppc64le 🎆
|
||||
- [Custom VPN server side port forwarding for Private Internet Access](https://github.com/qdm12/gluetun/wiki/Private-internet-access#vpn-server-port-forwarding)
|
||||
- Possibility of split horizon DNS by selecting multiple DNS over TLS providers
|
||||
- Unbound subprogram drops root privileges once launched
|
||||
- Can work as a Kubernetes sidecar container, thanks @rorph
|
||||
|
||||
## Setup
|
||||
|
||||
🎉 There are now instructions specific to each VPN provider with examples to help you get started as quickly as possible!
|
||||
|
||||
Go to the [Wiki](https://github.com/qdm12/gluetun/wiki)!
|
||||
|
||||
[🐛 Found a bug in the Wiki?!](https://github.com/qdm12/gluetun/issues/new?assignees=&labels=%F0%9F%93%84+Wiki+issue&template=wiki+issue.yml&title=Wiki+issue%3A+)
|
||||
|
||||
Here's a docker-compose.yml for the laziest:
|
||||
|
||||
```yml
|
||||
version: "3"
|
||||
services:
|
||||
gluetun:
|
||||
image: qmcgaw/gluetun
|
||||
# container_name: gluetun
|
||||
# line above must be uncommented to allow external containers to connect. See https://github.com/qdm12/gluetun/wiki/Connect-a-container-to-gluetun#external-container-to-gluetun
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
ports:
|
||||
- 8888:8888/tcp # HTTP proxy
|
||||
- 8388:8388/tcp # Shadowsocks
|
||||
- 8388:8388/udp # Shadowsocks
|
||||
volumes:
|
||||
- /yourpath:/gluetun
|
||||
environment:
|
||||
# See https://github.com/qdm12/gluetun/wiki
|
||||
- VPNSP=ivpn
|
||||
- VPN_TYPE=openvpn
|
||||
# OpenVPN:
|
||||
- OPENVPN_USER=
|
||||
- OPENVPN_PASSWORD=
|
||||
# Wireguard:
|
||||
# - WIREGUARD_PRIVATE_KEY=wOEI9rqqbDwnN8/Bpp22sVz48T71vJ4fYmFWujulwUU=
|
||||
# - WIREGUARD_ADDRESS=10.64.222.21/32
|
||||
# Timezone for accurate log times
|
||||
- TZ=
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
[](https://github.com/qdm12/gluetun/master/LICENSE)
|
||||
# Gluetun VPN client
|
||||
|
||||
Lightweight swiss-knife-like VPN client to multiple VPN service providers
|
||||
|
||||

|
||||
|
||||
[](https://github.com/qdm12/gluetun/actions/workflows/ci.yml)
|
||||
|
||||
[](https://hub.docker.com/r/qmcgaw/gluetun)
|
||||
[](https://hub.docker.com/r/qmcgaw/gluetun)
|
||||
|
||||
[](https://hub.docker.com/r/qmcgaw/gluetun)
|
||||
[](https://hub.docker.com/r/qmcgaw/gluetun)
|
||||
|
||||

|
||||

|
||||
[](https://hub.docker.com/r/qmcgaw/gluetun/tags?page=1&ordering=last_updated)
|
||||

|
||||

|
||||
|
||||
[](https://hub.docker.com/r/qmcgaw/gluetun/tags)
|
||||
|
||||
[](https://github.com/qdm12/gluetun/commits/master)
|
||||
[](https://github.com/qdm12/gluetun/graphs/contributors)
|
||||
[](https://github.com/qdm12/gluetun/pulls?q=is%3Apr+is%3Aclosed)
|
||||
[](https://github.com/qdm12/gluetun/issues)
|
||||
[](https://github.com/qdm12/gluetun/issues?q=is%3Aissue+is%3Aclosed)
|
||||
|
||||
[](https://github.com/qdm12/gluetun)
|
||||

|
||||

|
||||

|
||||
|
||||

|
||||
|
||||
## Quick links
|
||||
|
||||
- [Setup](#Setup)
|
||||
- [Features](#Features)
|
||||
- Problem?
|
||||
- [Check the Wiki](https://github.com/qdm12/gluetun/wiki)
|
||||
- [Start a discussion](https://github.com/qdm12/gluetun/discussions)
|
||||
- [Fix the Unraid template](https://github.com/qdm12/gluetun/discussions/550)
|
||||
- Suggestion?
|
||||
- [Create an issue](https://github.com/qdm12/gluetun/issues)
|
||||
- [Join the Slack channel](https://join.slack.com/t/qdm12/shared_invite/enQtOTE0NjcxNTM1ODc5LTYyZmVlOTM3MGI4ZWU0YmJkMjUxNmQ4ODQ2OTAwYzMxMTlhY2Q1MWQyOWUyNjc2ODliNjFjMDUxNWNmNzk5MDk)
|
||||
- Happy?
|
||||
- Sponsor me on [github.com/sponsors/qdm12](https://github.com/sponsors/qdm12)
|
||||
- Donate to [paypal.me/qmcgaw](https://www.paypal.me/qmcgaw)
|
||||
- Drop me [an email](mailto:quentin.mcgaw@gmail.com)
|
||||
- **Want to add a VPN provider?** check [Development](https://github.com/qdm12/gluetun/wiki/Development) and [Add a provider](https://github.com/qdm12/gluetun/wiki/Add-a-provider)
|
||||
- Video:
|
||||
|
||||
[](https://youtu.be/0F6I03LQcI4)
|
||||
|
||||
- [Substack Console interview](https://console.substack.com/p/console-72)
|
||||
|
||||
## Features
|
||||
|
||||
- Based on Alpine 3.17 for a small Docker image of 42MB
|
||||
- Supports: **AirVPN**, **Cyberghost**, **ExpressVPN**, **FastestVPN**, **HideMyAss**, **IPVanish**, **IVPN**, **Mullvad**, **NordVPN**, **Perfect Privacy**, **Privado**, **Private Internet Access**, **PrivateVPN**, **ProtonVPN**, **PureVPN**, **SlickVPN**, **Surfshark**, **TorGuard**, **VPNSecure.me**, **VPNUnlimited**, **Vyprvpn**, **WeVPN**, **Windscribe** servers
|
||||
- Supports OpenVPN for all providers listed
|
||||
- Supports Wireguard both kernelspace and userspace
|
||||
- For **Mullvad**, **Ivpn**, **Surfshark** and **Windscribe**
|
||||
- For **ProtonVPN**, **PureVPN**, **Torguard**, **VPN Unlimited** and **WeVPN** using [the custom provider](https://github.com/qdm12/gluetun/wiki/Custom-provider)
|
||||
- For custom Wireguard configurations using [the custom provider](https://github.com/qdm12/gluetun/wiki/Custom-provider)
|
||||
- More in progress, see [#134](https://github.com/qdm12/gluetun/issues/134)
|
||||
- DNS over TLS baked in with service provider(s) of your choice
|
||||
- DNS fine blocking of malicious/ads/surveillance hostnames and IP addresses, with live update every 24 hours
|
||||
- Choose the vpn network protocol, `udp` or `tcp`
|
||||
- Built in firewall kill switch to allow traffic only with needed the VPN servers and LAN devices
|
||||
- Built in Shadowsocks proxy (protocol based on SOCKS5 with an encryption layer, tunnels TCP+UDP)
|
||||
- Built in HTTP proxy (tunnels HTTP and HTTPS through TCP)
|
||||
- [Connect other containers to it](https://github.com/qdm12/gluetun/wiki/Connect-a-container-to-gluetun)
|
||||
- [Connect LAN devices to it](https://github.com/qdm12/gluetun/wiki/Connect-a-LAN-device-to-gluetun)
|
||||
- Compatible with amd64, i686 (32 bit), **ARM** 64 bit, ARM 32 bit v6 and v7, and even ppc64le 🎆
|
||||
- [Custom VPN server side port forwarding for Private Internet Access](https://github.com/qdm12/gluetun/wiki/Private-internet-access#vpn-server-port-forwarding)
|
||||
- Possibility of split horizon DNS by selecting multiple DNS over TLS providers
|
||||
- Unbound subprogram drops root privileges once launched
|
||||
- Can work as a Kubernetes sidecar container, thanks @rorph
|
||||
|
||||
## Setup
|
||||
|
||||
🎉 There are now instructions specific to each VPN provider with examples to help you get started as quickly as possible!
|
||||
|
||||
Go to the [Wiki](https://github.com/qdm12/gluetun/wiki)!
|
||||
|
||||
[🐛 Found a bug in the Wiki?!](https://github.com/qdm12/gluetun/issues/new?assignees=&labels=%F0%9F%93%84+Wiki+issue&template=wiki+issue.yml&title=Wiki+issue%3A+)
|
||||
|
||||
Here's a docker-compose.yml for the laziest:
|
||||
|
||||
```yml
|
||||
version: "3"
|
||||
services:
|
||||
gluetun:
|
||||
image: qmcgaw/gluetun
|
||||
# container_name: gluetun
|
||||
# line above must be uncommented to allow external containers to connect. See https://github.com/qdm12/gluetun/wiki/Connect-a-container-to-gluetun#external-container-to-gluetun
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
devices:
|
||||
- /dev/net/tun:/dev/net/tun
|
||||
ports:
|
||||
- 8888:8888/tcp # HTTP proxy
|
||||
- 8388:8388/tcp # Shadowsocks
|
||||
- 8388:8388/udp # Shadowsocks
|
||||
volumes:
|
||||
- /yourpath:/gluetun
|
||||
environment:
|
||||
# See https://github.com/qdm12/gluetun/wiki
|
||||
- VPN_SERVICE_PROVIDER=ivpn
|
||||
- VPN_TYPE=openvpn
|
||||
# OpenVPN:
|
||||
- OPENVPN_USER=
|
||||
- OPENVPN_PASSWORD=
|
||||
# Wireguard:
|
||||
# - WIREGUARD_PRIVATE_KEY=wOEI9rqqbDwnN8/Bpp22sVz48T71vJ4fYmFWujulwUU=
|
||||
# - WIREGUARD_ADDRESSES=10.64.222.21/32
|
||||
# Timezone for accurate log times
|
||||
- TZ=
|
||||
# Server list updater. See https://github.com/qdm12/gluetun/wiki/Updating-Servers#periodic-update
|
||||
- UPDATER_PERIOD=
|
||||
- UPDATER_VPN_SERVICE_PROVIDERS=
|
||||
```
|
||||
|
||||
🆕 Image also available as `ghcr.io/qdm12/gluetun`
|
||||
|
||||
## License
|
||||
|
||||
[](https://github.com/qdm12/gluetun/master/LICENSE)
|
||||
|
||||
@@ -16,10 +16,10 @@ import (
|
||||
"github.com/qdm12/dns/pkg/unbound"
|
||||
"github.com/qdm12/gluetun/internal/alpine"
|
||||
"github.com/qdm12/gluetun/internal/cli"
|
||||
"github.com/qdm12/gluetun/internal/configuration/sources"
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gluetun/internal/configuration/sources/env"
|
||||
"github.com/qdm12/gluetun/internal/configuration/sources/files"
|
||||
"github.com/qdm12/gluetun/internal/configuration/sources/mux"
|
||||
mux "github.com/qdm12/gluetun/internal/configuration/sources/merge"
|
||||
"github.com/qdm12/gluetun/internal/configuration/sources/secrets"
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/dns"
|
||||
@@ -29,22 +29,28 @@ import (
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/qdm12/gluetun/internal/netlink"
|
||||
"github.com/qdm12/gluetun/internal/openvpn"
|
||||
"github.com/qdm12/gluetun/internal/openvpn/extract"
|
||||
"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/ipinfo"
|
||||
"github.com/qdm12/gluetun/internal/routing"
|
||||
"github.com/qdm12/gluetun/internal/server"
|
||||
"github.com/qdm12/gluetun/internal/shadowsocks"
|
||||
"github.com/qdm12/gluetun/internal/storage"
|
||||
"github.com/qdm12/gluetun/internal/tun"
|
||||
"github.com/qdm12/gluetun/internal/updater"
|
||||
updater "github.com/qdm12/gluetun/internal/updater/loop"
|
||||
"github.com/qdm12/gluetun/internal/updater/resolver"
|
||||
"github.com/qdm12/gluetun/internal/updater/unzip"
|
||||
"github.com/qdm12/gluetun/internal/vpn"
|
||||
"github.com/qdm12/golibs/command"
|
||||
"github.com/qdm12/golibs/logging"
|
||||
"github.com/qdm12/goshutdown"
|
||||
"github.com/qdm12/goshutdown/goroutine"
|
||||
"github.com/qdm12/goshutdown/group"
|
||||
"github.com/qdm12/goshutdown/order"
|
||||
"github.com/qdm12/gosplash"
|
||||
"github.com/qdm12/log"
|
||||
"github.com/qdm12/updated/pkg/dnscrypto"
|
||||
)
|
||||
|
||||
@@ -55,11 +61,6 @@ var (
|
||||
created = "an unknown date"
|
||||
)
|
||||
|
||||
var (
|
||||
errSetupRouting = errors.New("cannot setup routing")
|
||||
errCreateUser = errors.New("cannot create user")
|
||||
)
|
||||
|
||||
func main() {
|
||||
buildInfo := models.BuildInformation{
|
||||
Version: version,
|
||||
@@ -68,16 +69,16 @@ func main() {
|
||||
}
|
||||
|
||||
background := context.Background()
|
||||
signalCtx, stop := signal.NotifyContext(background, syscall.SIGINT, syscall.SIGTERM, os.Interrupt)
|
||||
signalCh := make(chan os.Signal, 1)
|
||||
signal.Notify(signalCh, os.Interrupt, syscall.SIGTERM)
|
||||
ctx, cancel := context.WithCancel(background)
|
||||
|
||||
logger := logging.New(logging.Settings{
|
||||
Level: logging.LevelInfo,
|
||||
})
|
||||
logger := log.New(log.SetLevel(log.LevelInfo))
|
||||
|
||||
args := os.Args
|
||||
tun := tun.New()
|
||||
netLinker := netlink.New()
|
||||
netLinkDebugLogger := logger.New(log.SetComponent("netlink"))
|
||||
netLinker := netlink.New(netLinkDebugLogger)
|
||||
cli := cli.New()
|
||||
cmder := command.NewCmder()
|
||||
|
||||
@@ -91,14 +92,13 @@ func main() {
|
||||
errorCh <- _main(ctx, buildInfo, args, logger, muxReader, tun, netLinker, cmder, cli)
|
||||
}()
|
||||
|
||||
var err error
|
||||
select {
|
||||
case <-signalCtx.Done():
|
||||
stop()
|
||||
case signal := <-signalCh:
|
||||
fmt.Println("")
|
||||
logger.Warn("Caught OS signal, shutting down")
|
||||
logger.Warn("Caught OS signal " + signal.String() + ", shutting down")
|
||||
cancel()
|
||||
case err := <-errorCh:
|
||||
stop()
|
||||
case err = <-errorCh:
|
||||
close(errorCh)
|
||||
if err == nil { // expected exit such as healthcheck
|
||||
os.Exit(0)
|
||||
@@ -110,27 +110,38 @@ func main() {
|
||||
const shutdownGracePeriod = 5 * time.Second
|
||||
timer := time.NewTimer(shutdownGracePeriod)
|
||||
select {
|
||||
case <-errorCh:
|
||||
case shutdownErr := <-errorCh:
|
||||
if !timer.Stop() {
|
||||
<-timer.C
|
||||
}
|
||||
if shutdownErr != nil {
|
||||
logger.Warnf("Shutdown not completed gracefully: %s", shutdownErr)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
logger.Info("Shutdown successful")
|
||||
if err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
os.Exit(0)
|
||||
case <-timer.C:
|
||||
logger.Warn("Shutdown timed out")
|
||||
os.Exit(1)
|
||||
case signal := <-signalCh:
|
||||
logger.Warn("Caught OS signal " + signal.String() + ", forcing shut down")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
var (
|
||||
errCommandUnknown = errors.New("command is unknown")
|
||||
)
|
||||
|
||||
//nolint:gocognit,gocyclo
|
||||
//nolint:gocognit,gocyclo,maintidx
|
||||
func _main(ctx context.Context, buildInfo models.BuildInformation,
|
||||
args []string, logger logging.ParentLogger, source sources.Source,
|
||||
tun tun.Interface, netLinker netlink.NetLinker, cmder command.RunStarter,
|
||||
cli cli.CLIer) error {
|
||||
args []string, logger log.LoggerInterface, source Source,
|
||||
tun Tun, netLinker netLinker, cmder command.RunStarter,
|
||||
cli clier) error {
|
||||
if len(args) > 1 { // cli operation
|
||||
switch args[1] {
|
||||
case "healthcheck":
|
||||
@@ -138,7 +149,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
||||
case "clientkey":
|
||||
return cli.ClientKey(args[2:])
|
||||
case "openvpnconfig":
|
||||
return cli.OpenvpnConfig(logger, source)
|
||||
return cli.OpenvpnConfig(logger, source, netLinker)
|
||||
case "update":
|
||||
return cli.Update(ctx, args[2:], logger)
|
||||
case "format-servers":
|
||||
@@ -174,21 +185,68 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
||||
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))
|
||||
netLinker.PatchLoggerLevel(*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
|
||||
storageLogger := logger.NewChild(logging.Settings{Prefix: "storage: "})
|
||||
storageLogger := logger.New(log.SetComponent("storage"))
|
||||
storage, err := storage.New(storageLogger, constants.ServersData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
allServers := storage.GetServers()
|
||||
ipv6Supported, err := netLinker.IsIPv6Supported()
|
||||
if err != nil {
|
||||
return fmt.Errorf("checking for IPv6 support: %w", err)
|
||||
}
|
||||
|
||||
err = allSettings.Validate(allServers)
|
||||
err = allSettings.Validate(storage, ipv6Supported)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logger.PatchLevel(*allSettings.Log.Level)
|
||||
allSettings.Pprof.HTTPServer.Logger = logger.New(log.SetComponent("pprof"))
|
||||
pprofServer, err := pprof.New(allSettings.Pprof)
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating Pprof server: %w", err)
|
||||
}
|
||||
|
||||
puid, pgid := int(*allSettings.System.PUID), int(*allSettings.System.PGID)
|
||||
|
||||
@@ -197,7 +255,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
||||
// Create configurators
|
||||
alpineConf := alpine.New()
|
||||
ovpnConf := openvpn.New(
|
||||
logger.NewChild(logging.Settings{Prefix: "openvpn configurator: "}),
|
||||
logger.New(log.SetComponent("openvpn configurator")),
|
||||
cmder, puid, pgid)
|
||||
dnsCrypto := dnscrypto.New(httpClient, "", "")
|
||||
const cacertsPath = "/etc/ssl/certs/ca-certificates.crt"
|
||||
@@ -219,6 +277,10 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
||||
|
||||
logger.Info(allSettings.String())
|
||||
|
||||
for _, warning := range allSettings.Warnings() {
|
||||
logger.Warn(warning)
|
||||
}
|
||||
|
||||
if err := os.MkdirAll("/tmp/gluetun", 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -229,7 +291,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
||||
const defaultUsername = "nonrootuser"
|
||||
nonRootUsername, err := alpineConf.CreateUser(defaultUsername, puid)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %s", errCreateUser, err)
|
||||
return fmt.Errorf("creating user: %w", err)
|
||||
}
|
||||
if nonRootUsername != defaultUsername {
|
||||
logger.Info("using existing username " + nonRootUsername + " corresponding to user id " + fmt.Sprint(puid))
|
||||
@@ -237,54 +299,22 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
||||
// set it for Unbound
|
||||
// TODO remove this when migrating to qdm12/dns v2
|
||||
allSettings.DNS.DoT.Unbound.Username = nonRootUsername
|
||||
allSettings.VPN.OpenVPN.ProcUser = nonRootUsername
|
||||
allSettings.VPN.OpenVPN.ProcessUser = nonRootUsername
|
||||
|
||||
if err := os.Chown("/etc/unbound", puid, pgid); err != nil {
|
||||
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 strings.Contains(err.Error(), "operation not permitted") {
|
||||
logger.Warn("💡 Tip: Are you passing NET_ADMIN capability to gluetun?")
|
||||
}
|
||||
return fmt.Errorf("%w: %s", errSetupRouting, err)
|
||||
return fmt.Errorf("setting up routing: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
logger.Info("routing cleanup...")
|
||||
routingLogger.Info("routing cleanup...")
|
||||
if err := routingConf.TearDown(); err != nil {
|
||||
logger.Error("cannot teardown routing: " + err.Error())
|
||||
routingLogger.Error("cannot teardown routing: " + err.Error())
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -295,25 +325,21 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
||||
return err
|
||||
}
|
||||
|
||||
if err := tun.Check(constants.TunnelDevice); err != nil {
|
||||
const tunDevice = "/dev/net/tun"
|
||||
if err := tun.Check(tunDevice); err != nil {
|
||||
logger.Info(err.Error() + "; creating it...")
|
||||
err = tun.Create(constants.TunnelDevice)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if *allSettings.Firewall.Enabled {
|
||||
err := firewallConf.SetEnabled(ctx, true) // disabled by default
|
||||
err = tun.Create(tunDevice)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, port := range allSettings.Firewall.InputPorts {
|
||||
err = firewallConf.SetAllowedPort(ctx, port, defaultInterface)
|
||||
if err != nil {
|
||||
return err
|
||||
for _, defaultRoute := range defaultRoutes {
|
||||
err = firewallConf.SetAllowedPort(ctx, port, defaultRoute.NetInterface)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} // TODO move inside firewall?
|
||||
|
||||
@@ -334,14 +360,23 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
||||
tickersGroupHandler := goshutdown.NewGroupHandler("tickers", defaultGroupOptions...)
|
||||
otherGroupHandler := goshutdown.NewGroupHandler("other", defaultGroupOptions...)
|
||||
|
||||
portForwardLogger := logger.NewChild(logging.Settings{Prefix: "port forwarding: "})
|
||||
if *allSettings.Pprof.Enabled {
|
||||
// TODO run in run loop so this can be patched at runtime
|
||||
pprofReady := make(chan struct{})
|
||||
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,
|
||||
httpClient, firewallConf, portForwardLogger)
|
||||
httpClient, firewallConf, portForwardLogger, puid, pgid)
|
||||
portForwardHandler, portForwardCtx, portForwardDone := goshutdown.NewGoRoutineHandler(
|
||||
"port forwarding", goroutine.OptionTimeout(time.Second))
|
||||
go portForwardLooper.Run(portForwardCtx, portForwardDone)
|
||||
|
||||
unboundLogger := logger.NewChild(logging.Settings{Prefix: "dns over tls: "})
|
||||
unboundLogger := logger.New(log.SetComponent("dns over tls"))
|
||||
unboundLooper := dns.NewLoop(dnsConf, allSettings.DNS, httpClient,
|
||||
unboundLogger)
|
||||
dnsHandler, dnsCtx, dnsDone := goshutdown.NewGoRoutineHandler(
|
||||
@@ -355,8 +390,9 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
||||
go unboundLooper.RunRestartTicker(dnsTickerCtx, dnsTickerDone)
|
||||
controlGroupHandler.Add(dnsTickerHandler)
|
||||
|
||||
publicIPLooper := publicip.NewLoop(httpClient,
|
||||
logger.NewChild(logging.Settings{Prefix: "ip getter: "}),
|
||||
ipFetcher := ipinfo.New(httpClient)
|
||||
publicIPLooper := publicip.NewLoop(ipFetcher,
|
||||
logger.New(log.SetComponent("ip getter")),
|
||||
allSettings.PublicIP, puid, pgid)
|
||||
pubIPHandler, pubIPCtx, pubIPDone := goshutdown.NewGoRoutineHandler(
|
||||
"public IP", goroutine.OptionTimeout(defaultShutdownTimeout))
|
||||
@@ -368,18 +404,25 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
||||
go publicIPLooper.RunRestartTicker(pubIPTickerCtx, pubIPTickerDone)
|
||||
tickersGroupHandler.Add(pubIPTickerHandler)
|
||||
|
||||
vpnLogger := logger.NewChild(logging.Settings{Prefix: "vpn: "})
|
||||
vpnLooper := vpn.NewLoop(allSettings.VPN, allSettings.Firewall.VPNInputPorts,
|
||||
allServers, ovpnConf, netLinker, firewallConf, routingConf, portForwardLooper,
|
||||
updaterLogger := logger.New(log.SetComponent("updater"))
|
||||
|
||||
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, ipv6Supported, allSettings.Firewall.VPNInputPorts,
|
||||
providers, storage, ovpnConf, netLinker, firewallConf, routingConf, portForwardLooper,
|
||||
cmder, publicIPLooper, unboundLooper, vpnLogger, httpClient,
|
||||
buildInfo, *allSettings.Version.Enabled)
|
||||
vpnHandler, vpnCtx, vpnDone := goshutdown.NewGoRoutineHandler(
|
||||
"vpn", goroutine.OptionTimeout(time.Second))
|
||||
go vpnLooper.Run(vpnCtx, vpnDone)
|
||||
|
||||
updaterLooper := updater.NewLooper(allSettings.Updater,
|
||||
allServers, storage, vpnLooper.SetServers, httpClient,
|
||||
logger.NewChild(logging.Settings{Prefix: "updater: "}))
|
||||
updaterLooper := updater.NewLoop(allSettings.Updater,
|
||||
providers, storage, httpClient, updaterLogger)
|
||||
updaterHandler, updaterCtx, updaterDone := goshutdown.NewGoRoutineHandler(
|
||||
"updater", goroutine.OptionTimeout(defaultShutdownTimeout))
|
||||
// wait for updaterLooper.Restart() or its ticket launched with RunRestartTicker
|
||||
@@ -392,31 +435,37 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
||||
controlGroupHandler.Add(updaterTickerHandler)
|
||||
|
||||
httpProxyLooper := httpproxy.NewLoop(
|
||||
logger.NewChild(logging.Settings{Prefix: "http proxy: "}),
|
||||
logger.New(log.SetComponent("http proxy")),
|
||||
allSettings.HTTPProxy)
|
||||
httpProxyHandler, httpProxyCtx, httpProxyDone := goshutdown.NewGoRoutineHandler(
|
||||
"http proxy", goroutine.OptionTimeout(defaultShutdownTimeout))
|
||||
go httpProxyLooper.Run(httpProxyCtx, httpProxyDone)
|
||||
otherGroupHandler.Add(httpProxyHandler)
|
||||
|
||||
shadowsocksLooper := shadowsocks.NewLooper(allSettings.Shadowsocks,
|
||||
logger.NewChild(logging.Settings{Prefix: "shadowsocks: "}))
|
||||
shadowsocksLooper := shadowsocks.NewLoop(allSettings.Shadowsocks,
|
||||
logger.New(log.SetComponent("shadowsocks")))
|
||||
shadowsocksHandler, shadowsocksCtx, shadowsocksDone := goshutdown.NewGoRoutineHandler(
|
||||
"shadowsocks proxy", goroutine.OptionTimeout(defaultShutdownTimeout))
|
||||
go shadowsocksLooper.Run(shadowsocksCtx, shadowsocksDone)
|
||||
otherGroupHandler.Add(shadowsocksHandler)
|
||||
|
||||
controlServerAddress := fmt.Sprintf(":%d", *allSettings.ControlServer.Port)
|
||||
controlServerAddress := *allSettings.ControlServer.Address
|
||||
controlServerLogging := *allSettings.ControlServer.Log
|
||||
httpServerHandler, httpServerCtx, httpServerDone := goshutdown.NewGoRoutineHandler(
|
||||
"http server", goroutine.OptionTimeout(defaultShutdownTimeout))
|
||||
httpServer := server.New(httpServerCtx, controlServerAddress, controlServerLogging,
|
||||
logger.NewChild(logging.Settings{Prefix: "http server: "}),
|
||||
buildInfo, vpnLooper, portForwardLooper, unboundLooper, updaterLooper, publicIPLooper)
|
||||
go httpServer.Run(httpServerCtx, httpServerDone)
|
||||
httpServer, err := server.New(httpServerCtx, controlServerAddress, controlServerLogging,
|
||||
logger.New(log.SetComponent("http server")),
|
||||
buildInfo, vpnLooper, portForwardLooper, unboundLooper, updaterLooper, publicIPLooper,
|
||||
storage, ipv6Supported)
|
||||
if err != nil {
|
||||
return fmt.Errorf("setting up control server: %w", err)
|
||||
}
|
||||
httpServerReady := make(chan struct{})
|
||||
go httpServer.Run(httpServerCtx, httpServerReady, httpServerDone)
|
||||
<-httpServerReady
|
||||
controlGroupHandler.Add(httpServerHandler)
|
||||
|
||||
healthLogger := logger.NewChild(logging.Settings{Prefix: "healthcheck: "})
|
||||
healthLogger := logger.New(log.SetComponent("healthcheck"))
|
||||
healthcheckServer := healthcheck.NewServer(allSettings.Health, healthLogger, vpnLooper)
|
||||
healthServerHandler, healthServerCtx, healthServerDone := goshutdown.NewGoRoutineHandler(
|
||||
"HTTP health server", goroutine.OptionTimeout(defaultShutdownTimeout))
|
||||
@@ -456,10 +505,69 @@ func printVersions(ctx context.Context, logger infoer,
|
||||
for _, element := range elements {
|
||||
version, err := element.getVersion(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("getting %s version: %w", element.name, err)
|
||||
}
|
||||
logger.Info(element.name + " version: " + version)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type netLinker interface {
|
||||
Addresser
|
||||
Router
|
||||
Ruler
|
||||
Linker
|
||||
IsWireguardSupported() (ok bool, err error)
|
||||
IsIPv6Supported() (ok bool, err error)
|
||||
PatchLoggerLevel(level log.Level)
|
||||
}
|
||||
|
||||
type Addresser interface {
|
||||
AddrList(link netlink.Link, family int) (
|
||||
addresses []netlink.Addr, err error)
|
||||
AddrAdd(link netlink.Link, addr *netlink.Addr) error
|
||||
}
|
||||
|
||||
type Router interface {
|
||||
RouteList(link netlink.Link, family int) (
|
||||
routes []netlink.Route, err error)
|
||||
RouteAdd(route *netlink.Route) error
|
||||
RouteDel(route *netlink.Route) error
|
||||
RouteReplace(route *netlink.Route) error
|
||||
}
|
||||
|
||||
type Ruler interface {
|
||||
RuleList(family int) (rules []netlink.Rule, err error)
|
||||
RuleAdd(rule *netlink.Rule) error
|
||||
RuleDel(rule *netlink.Rule) error
|
||||
}
|
||||
|
||||
type Linker interface {
|
||||
LinkList() (links []netlink.Link, err error)
|
||||
LinkByName(name string) (link netlink.Link, err error)
|
||||
LinkByIndex(index int) (link netlink.Link, err error)
|
||||
LinkAdd(link netlink.Link) (err error)
|
||||
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 cli.Source, ipv6Checker cli.IPv6Checker) error
|
||||
HealthCheck(ctx context.Context, source cli.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
|
||||
}
|
||||
|
||||
type Source interface {
|
||||
Read() (settings settings.Settings, err error)
|
||||
ReadHealth() (health settings.Health, err error)
|
||||
String() string
|
||||
}
|
||||
|
||||
40
go.mod
40
go.mod
@@ -1,10 +1,10 @@
|
||||
module github.com/qdm12/gluetun
|
||||
|
||||
go 1.17
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/breml/rootcerts v0.2.1
|
||||
github.com/fatih/color v1.13.0
|
||||
github.com/breml/rootcerts v0.2.10
|
||||
github.com/fatih/color v1.14.1
|
||||
github.com/golang/mock v1.6.0
|
||||
github.com/qdm12/dns v1.11.0
|
||||
github.com/qdm12/golibs v0.0.0-20210822203818-5c568b0777b6
|
||||
@@ -12,32 +12,38 @@ require (
|
||||
github.com/qdm12/gosplash v0.1.0
|
||||
github.com/qdm12/gotree v0.2.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/updated v0.0.0-20210603204757-205acfe6937e
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/stretchr/testify v1.8.2
|
||||
github.com/vishvananda/netlink v1.1.1-0.20211129163951-9ada19101fc5
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c
|
||||
golang.zx2c4.com/wireguard v0.0.0-20210805125648-3957e9b9dd19
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20210803171230-4253848d036c
|
||||
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a
|
||||
golang.org/x/net v0.0.0-20220418201149-a630d4f3e7a2
|
||||
golang.org/x/sys v0.6.0
|
||||
golang.org/x/text v0.8.0
|
||||
golang.zx2c4.com/wireguard v0.0.0-20220703234212-c31a7b1ab478
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20220504211119-3d4a969bb56b
|
||||
inet.af/netaddr v0.0.0-20210718074554-06ca8145d722
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/google/go-cmp v0.5.5 // indirect
|
||||
github.com/josharian/native v0.0.0-20200817173448-b6b71def0850 // indirect
|
||||
github.com/mattn/go-colorable v0.1.9 // indirect
|
||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||
github.com/mdlayher/genetlink v1.0.0 // indirect
|
||||
github.com/mdlayher/netlink v1.4.0 // indirect
|
||||
github.com/google/go-cmp v0.5.7 // indirect
|
||||
github.com/josharian/native v1.0.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.17 // indirect
|
||||
github.com/mdlayher/genetlink v1.2.0 // indirect
|
||||
github.com/mdlayher/netlink v1.6.0 // indirect
|
||||
github.com/mdlayher/socket v0.2.3 // indirect
|
||||
github.com/miekg/dns v1.1.40 // indirect
|
||||
github.com/mr-tron/base58 v1.2.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
|
||||
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae // indirect
|
||||
go4.org/intern v0.0.0-20210108033219-3eb7198706b2 // 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/net v0.0.0-20210504132125-bbd867fde50d // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
|
||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20230221090011-e4bae7ad2296 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
|
||||
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
139
go.sum
139
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/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
||||
github.com/breml/rootcerts v0.2.1 h1:GZMVDXOs945764NFck0vtHSjktKYubOFM0kjf5HAuwc=
|
||||
github.com/breml/rootcerts v0.2.1/go.mod h1:24FDtzYMpqIeYC7QzaE8VPRQaFZU5TIUDlyk8qwjD88=
|
||||
github.com/breml/rootcerts v0.2.10 h1:UGVZ193UTSUASpGtg6pbDwzOd7XQP+at0Ssg1/2E4h8=
|
||||
github.com/breml/rootcerts v0.2.10/go.mod h1:24FDtzYMpqIeYC7QzaE8VPRQaFZU5TIUDlyk8qwjD88=
|
||||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
@@ -14,8 +14,8 @@ github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDD
|
||||
github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw=
|
||||
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
|
||||
github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
||||
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
|
||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||
github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w=
|
||||
github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg=
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
||||
github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
||||
@@ -36,28 +36,16 @@ github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3K
|
||||
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
|
||||
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
||||
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gotify/go-api-client/v2 v2.0.4/go.mod h1:VKiah/UK20bXsr0JObE1eBVLW44zbBouzjuri9iwjFU=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/josharian/native v0.0.0-20200817173448-b6b71def0850 h1:uhL5Gw7BINiiPAo24A2sxkcDI0Jt/sqp1v5xQCniEFA=
|
||||
github.com/josharian/native v0.0.0-20200817173448-b6b71def0850/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20201009170750-9c6f07d100c1/go.mod h1:hqoO/u39cqLeBLebZ8fWdE96O7FxrAsRYhnVOdgHxok=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20201216134343-bde56ed16391/go.mod h1:cR77jAZG3Y3bsb8hF6fHJbFoyFukLFOkQ98S0pQz3xw=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20201220180245-69540ac93943/go.mod h1:z4c53zj6Eex712ROyh8WI0ihysb5j2ROyV42iNogmAs=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20210122163228-8d122574c736/go.mod h1:ZXpIyOK59ZnN7J0BV99cZUPmsqDRZ3eq5X+st7u/oSA=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20210212075122-66c871082f2b h1:c3NTyLNozICy8B4mlMXemD3z/gXgQzVXZS/HqT+i3do=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20210212075122-66c871082f2b/go.mod h1:8w9Rh8m+aHZIG69YPGGem1i5VzoyRC8nw2kA8B+ik5U=
|
||||
github.com/josharian/native v1.0.0 h1:Ts/E8zCSEsG17dUqv7joXJFybuMLjQfWE04tsBODTxk=
|
||||
github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
@@ -68,29 +56,22 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kyokomi/emoji v2.2.4+incompatible/go.mod h1:mZ6aGCD7yk8j6QY6KICwnZ2pxoszVseX1DNoGtU2tBA=
|
||||
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U=
|
||||
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mdlayher/ethtool v0.0.0-20210210192532-2b88debcdd43 h1:WgyLFv10Ov49JAQI/ZLUkCZ7VJS3r74hwFIGXJsgZlY=
|
||||
github.com/mdlayher/ethtool v0.0.0-20210210192532-2b88debcdd43/go.mod h1:+t7E0lkKfbBsebllff1xdTmyJt8lH37niI6kwFk9OTo=
|
||||
github.com/mdlayher/genetlink v1.0.0 h1:OoHN1OdyEIkScEmRgxLEe2M9U8ClMytqA5niynLtfj0=
|
||||
github.com/mdlayher/genetlink v1.0.0/go.mod h1:0rJ0h4itni50A86M2kHcgS85ttZazNt7a8H2a2cw0Gc=
|
||||
github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA=
|
||||
github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M=
|
||||
github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY=
|
||||
github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o=
|
||||
github.com/mdlayher/netlink v1.2.0/go.mod h1:kwVW1io0AZy9A1E2YYgaD4Cj+C+GPkU6klXCMzIJ9p8=
|
||||
github.com/mdlayher/netlink v1.2.1/go.mod h1:bacnNlfhqHqqLo4WsYeXSqfyXkInQ9JneWI68v1KwSU=
|
||||
github.com/mdlayher/netlink v1.2.2-0.20210123213345-5cc92139ae3e/go.mod h1:bacnNlfhqHqqLo4WsYeXSqfyXkInQ9JneWI68v1KwSU=
|
||||
github.com/mdlayher/netlink v1.3.0/go.mod h1:xK/BssKuwcRXHrtN04UBkwQ6dY9VviGGuriDdoPSWys=
|
||||
github.com/mdlayher/netlink v1.4.0 h1:n3ARR+Fm0dDv37dj5wSWZXDKcy+U0zwcXS3zKMnSiT0=
|
||||
github.com/mdlayher/netlink v1.4.0/go.mod h1:dRJi5IABcZpBD2A3D0Mv/AiX8I9uDEu5oGkAVrekmf8=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
|
||||
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mdlayher/genetlink v1.2.0 h1:4yrIkRV5Wfk1WfpWTcoOlGmsWgQj3OtQN9ZsbrE+XtU=
|
||||
github.com/mdlayher/genetlink v1.2.0/go.mod h1:ra5LDov2KrUCZJiAtEvXXZBxGMInICMXIwshlJ+qRxQ=
|
||||
github.com/mdlayher/netlink v1.6.0 h1:rOHX5yl7qnlpiVkFWoqccueppMtXzeziFjWAjLg6sz0=
|
||||
github.com/mdlayher/netlink v1.6.0/go.mod h1:0o3PlBmGst1xve7wQ7j/hwpNaFaH4qCRyWCdcZk8/vA=
|
||||
github.com/mdlayher/socket v0.1.1/go.mod h1:mYV5YIZAfHh4dzDVzI8x8tWLWCliuX8Mon5Awbj+qDs=
|
||||
github.com/mdlayher/socket v0.2.3 h1:XZA2X2TjdOwNoNPVPclRCURoX/hokBY8nkTmRZFEheM=
|
||||
github.com/mdlayher/socket v0.2.3/go.mod h1:bz12/FozYNH/VbvC3q7TRIK/Y6dH1kCKsXaUeXi/FmY=
|
||||
github.com/miekg/dns v1.1.40 h1:pyyPFfGMnciYUk/mXpKkVmeMQjfXqt3FAJ2hy7tPiLA=
|
||||
github.com/miekg/dns v1.1.40/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721 h1:RlZweED6sbSArvlE924+mUcZuXKLBHA35U7LN621Bws=
|
||||
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721/go.mod h1:Ickgr2WtCLZ2MDGd4Gr0geeCH5HybhRJbonOgQpvSxc=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
|
||||
@@ -115,6 +96,8 @@ github.com/qdm12/gotree v0.2.0 h1:+58ltxkNLUyHtATFereAcOjBVfY6ETqRex8XK90Fb/c=
|
||||
github.com/qdm12/gotree v0.2.0/go.mod h1:1SdFaqKZuI46U1apbXIf25pDMNnrPuYLEqMF/qL4lY4=
|
||||
github.com/qdm12/govalid v0.1.0 h1:UIFVmuaAg0Q+h0GeyfcFEZ5sQ5KJPvRQwycC1/cqDN8=
|
||||
github.com/qdm12/govalid v0.1.0/go.mod h1:CyS/OEQdOvunBgrtIsW93fjd4jBkwZPBjGSpxq3NwA4=
|
||||
github.com/qdm12/log v0.1.0 h1:jYBd/xscHYpblzZAd2kjZp2YmuYHjAAfbTViJWxoPTw=
|
||||
github.com/qdm12/log v0.1.0/go.mod h1:Vchi5M8uBvHfPNIblN4mjXn/oSbiWguQIbsgF1zdQPI=
|
||||
github.com/qdm12/ss-server v0.4.0 h1:lMMYfDGc9P86Lyvd3+p8lK4hhgHUKDzjZC91FqJYkDU=
|
||||
github.com/qdm12/ss-server v0.4.0/go.mod h1:AY0p4huvPUPW+/CiWsJcDgT6sneDryk26VXSccPNCxY=
|
||||
github.com/qdm12/updated v0.0.0-20210603204757-205acfe6937e h1:4q+uFLawkaQRq3yARYLsjJPZd2wYwxn4g6G/5v0xW1g=
|
||||
@@ -125,100 +108,92 @@ github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAm
|
||||
github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/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/netns v0.0.0-20200728191858-db3c7e526aae h1:4hwBBUfQCFe3Cym0ZtKyq7L16eZUtYKs+BaHDN6mAns=
|
||||
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
||||
github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
|
||||
github.com/yl2chen/cidranger v1.0.2/go.mod h1:9U1yz7WPYDwf0vpNWFaeRh0bjwz5RVgRy/9UEQfHl0g=
|
||||
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a h1:fZHgsYlfvtyqToslyjUt3VOPF4J7aK/3MPcK7xp3PDk=
|
||||
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a/go.mod h1:ul22v+Nro/R083muKhosV54bj5niojjWZvU8xrevuH4=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
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/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-20230221090011-e4bae7ad2296 h1:QJ/xcIANMLApehfgPCHnfK1hZiaMmbaTVmPv7DAoTbo=
|
||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20230221090011-e4bae7ad2296/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
|
||||
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.0.0-20210503195802-e9a32991a82e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA=
|
||||
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201216054612-986b41b23924/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210504132125-bbd867fde50d h1:nTDGCTeAu2LhcsHTRzjyIUbZHCJ4QePArsm27Hka0UM=
|
||||
golang.org/x/net v0.0.0-20210504132125-bbd867fde50d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220418201149-a630d4f3e7a2 h1:6mzvA99KwZxbOrxww4EvWVQUnN1+xEu9tafK5ZxkYeA=
|
||||
golang.org/x/net v0.0.0-20220418201149-a630d4f3e7a2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201118182958-a01c418693c7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201218084310-7d0127a74742/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210110051926-789bb1bd4061/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210123111255-9b0068b26619/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210216163648-f7da38b97c65/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210309040221-94ec62e08169/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210503173754-0981d6026fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
|
||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
@@ -230,11 +205,12 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20210427022245-097af6e1351b/go.mod h1:a057zjmoc00UN7gVkaJt2sXVK523kMJcogDTEvPIasg=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20210805125648-3957e9b9dd19 h1:ab2jcw2W91Rz07eHAb8Lic7sFQKO0NhBftjv6m/gL/0=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20210805125648-3957e9b9dd19/go.mod h1:laHzsbfMhGSobUmruXWAyMKKHSqvIcrqZJMyHD+/3O8=
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20210803171230-4253848d036c h1:ADNrRDI5NR23/TUCnEmlLZLt4u9DnZ2nwRkPrAcFvto=
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20210803171230-4253848d036c/go.mod h1:+1XihzyZUBJcSc5WO9SwNA7v26puQwOEDwanaxfNXPQ=
|
||||
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 h1:Ug9qvr1myri/zFN6xL17LSCBGFDnphBBhzmILHsM5TY=
|
||||
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20220703234212-c31a7b1ab478 h1:vDy//hdR+GnROE3OdYbQKt9rdtNdHkDtONvpRwmls/0=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20220703234212-c31a7b1ab478/go.mod h1:bVQfyl2sCM/QIIGHpWbFGfHPuDvqnCNkT6MQLTCjO/U=
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20220504211119-3d4a969bb56b h1:9JncmKXcUwE918my+H6xmjBdhK2jM/UTUNXxhRG1BAk=
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20220504211119-3d4a969bb56b/go.mod h1:yp4gl6zOlnDGOZeWeDfMwQcsdOIQnMdhuPx9mwwWBL4=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
@@ -244,8 +220,9 @@ 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/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.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.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-20210718074554-06ca8145d722 h1:Qws2rZnQudC58cIagVucPQDLmMi3kAXgxscsgD0v6DU=
|
||||
inet.af/netaddr v0.0.0-20210718074554-06ca8145d722/go.mod h1:z0nx+Dh+7N7CC8V5ayHtHGpZpxLQZZxkIaaz6HN65Ls=
|
||||
|
||||
@@ -1,17 +1,9 @@
|
||||
// Package alpine defines a configurator to interact with the Alpine operating system.
|
||||
package alpine
|
||||
|
||||
import (
|
||||
"os/user"
|
||||
)
|
||||
|
||||
var _ Alpiner = (*Alpine)(nil)
|
||||
|
||||
type Alpiner interface {
|
||||
UserCreater
|
||||
VersionGetter
|
||||
}
|
||||
|
||||
type Alpine struct {
|
||||
alpineReleasePath string
|
||||
passwdPath string
|
||||
|
||||
@@ -12,10 +12,6 @@ var (
|
||||
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.
|
||||
func (a *Alpine) CreateUser(username string, uid int) (createdUsername string, err error) {
|
||||
UIDStr := strconv.Itoa(uid)
|
||||
|
||||
@@ -7,10 +7,6 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
type VersionGetter interface {
|
||||
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)
|
||||
if err != nil {
|
||||
|
||||
@@ -1,16 +1,5 @@
|
||||
// Package cli defines an interface CLI to run command line operations.
|
||||
package cli
|
||||
|
||||
var _ CLIer = (*CLI)(nil)
|
||||
|
||||
type CLIer interface {
|
||||
ClientKeyFormatter
|
||||
HealthChecker
|
||||
OpenvpnConfigMaker
|
||||
Updater
|
||||
ServersFormatter
|
||||
}
|
||||
|
||||
type CLI struct {
|
||||
repoServersPath string
|
||||
}
|
||||
|
||||
@@ -7,16 +7,12 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/configuration/sources/files"
|
||||
)
|
||||
|
||||
type ClientKeyFormatter interface {
|
||||
ClientKey(args []string) error
|
||||
}
|
||||
|
||||
func (c *CLI) ClientKey(args []string) error {
|
||||
flagSet := flag.NewFlagSet("clientkey", flag.ExitOnError)
|
||||
filepath := flagSet.String("path", constants.ClientKey, "file path to the client.key file")
|
||||
filepath := flagSet.String("path", files.OpenVPNClientKeyPath, "file path to the client.key file")
|
||||
if err := flagSet.Parse(args); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -6,51 +6,44 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||
"github.com/qdm12/gluetun/internal/storage"
|
||||
"golang.org/x/text/cases"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
type ServersFormatter interface {
|
||||
FormatServers(args []string) error
|
||||
}
|
||||
|
||||
var (
|
||||
ErrFormatNotRecognized = errors.New("format is not recognized")
|
||||
ErrProviderUnspecified = errors.New("VPN provider to format was not specified")
|
||||
ErrOpenOutputFile = errors.New("cannot open output file")
|
||||
ErrWriteOutput = errors.New("cannot write to output file")
|
||||
ErrCloseOutputFile = errors.New("cannot close output file")
|
||||
ErrFormatNotRecognized = errors.New("format is not recognized")
|
||||
ErrProviderUnspecified = errors.New("VPN provider to format was not specified")
|
||||
ErrMultipleProvidersToFormat = errors.New("more than one VPN provider to format were specified")
|
||||
)
|
||||
|
||||
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 {
|
||||
var format, output string
|
||||
var cyberghost, expressvpn, fastestvpn, hideMyAss, ipvanish, ivpn, mullvad,
|
||||
nordvpn, perfectPrivacy, pia, privado, privatevpn, protonvpn, purevpn, surfshark,
|
||||
torguard, vpnUnlimited, vyprvpn, wevpn, windscribe bool
|
||||
allProviders := providers.All()
|
||||
providersToFormat := make(map[string]*bool, len(allProviders))
|
||||
for _, provider := range allProviders {
|
||||
providersToFormat[provider] = new(bool)
|
||||
}
|
||||
flagSet := flag.NewFlagSet("markdown", flag.ExitOnError)
|
||||
flagSet.StringVar(&format, "format", "markdown", "Format to use which can be: 'markdown'")
|
||||
flagSet.StringVar(&output, "output", "/dev/stdout", "Output file to write the formatted data to")
|
||||
flagSet.BoolVar(&cyberghost, "cyberghost", false, "Format Cyberghost servers")
|
||||
flagSet.BoolVar(&expressvpn, "expressvpn", false, "Format ExpressVPN servers")
|
||||
flagSet.BoolVar(&fastestvpn, "fastestvpn", false, "Format FastestVPN servers")
|
||||
flagSet.BoolVar(&hideMyAss, "hidemyass", false, "Format HideMyAss servers")
|
||||
flagSet.BoolVar(&ipvanish, "ipvanish", false, "Format IpVanish servers")
|
||||
flagSet.BoolVar(&ivpn, "ivpn", false, "Format IVPN servers")
|
||||
flagSet.BoolVar(&mullvad, "mullvad", false, "Format Mullvad servers")
|
||||
flagSet.BoolVar(&nordvpn, "nordvpn", false, "Format Nordvpn servers")
|
||||
flagSet.BoolVar(&perfectPrivacy, "perfectprivacy", false, "Format Perfect Privacy servers")
|
||||
flagSet.BoolVar(&pia, "pia", false, "Format Private Internet Access servers")
|
||||
flagSet.BoolVar(&privado, "privado", false, "Format Privado servers")
|
||||
flagSet.BoolVar(&privatevpn, "privatevpn", false, "Format Private VPN servers")
|
||||
flagSet.BoolVar(&protonvpn, "protonvpn", false, "Format Protonvpn servers")
|
||||
flagSet.BoolVar(&purevpn, "purevpn", false, "Format Purevpn servers")
|
||||
flagSet.BoolVar(&surfshark, "surfshark", false, "Format Surfshark servers")
|
||||
flagSet.BoolVar(&torguard, "torguard", false, "Format Torguard servers")
|
||||
flagSet.BoolVar(&vpnUnlimited, "vpnunlimited", false, "Format VPN Unlimited servers")
|
||||
flagSet.BoolVar(&vyprvpn, "vyprvpn", false, "Format Vyprvpn servers")
|
||||
flagSet.BoolVar(&wevpn, "wevpn", false, "Format WeVPN servers")
|
||||
flagSet.BoolVar(&windscribe, "windscribe", false, "Format Windscribe servers")
|
||||
titleCaser := cases.Title(language.English)
|
||||
for _, provider := range allProviders {
|
||||
addProviderFlag(flagSet, providersToFormat, provider, titleCaser)
|
||||
}
|
||||
if err := flagSet.Parse(args); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -59,74 +52,47 @@ func (c *CLI) FormatServers(args []string) error {
|
||||
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()
|
||||
storage, err := storage.New(logger, constants.ServersData)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %s", ErrNewStorage, err)
|
||||
return fmt.Errorf("creating servers storage: %w", err)
|
||||
}
|
||||
currentServers := storage.GetServers()
|
||||
|
||||
var formatted string
|
||||
switch {
|
||||
case cyberghost:
|
||||
formatted = currentServers.Cyberghost.ToMarkdown()
|
||||
case expressvpn:
|
||||
formatted = currentServers.Expressvpn.ToMarkdown()
|
||||
case fastestvpn:
|
||||
formatted = currentServers.Fastestvpn.ToMarkdown()
|
||||
case hideMyAss:
|
||||
formatted = currentServers.HideMyAss.ToMarkdown()
|
||||
case ipvanish:
|
||||
formatted = currentServers.Ipvanish.ToMarkdown()
|
||||
case ivpn:
|
||||
formatted = currentServers.Ivpn.ToMarkdown()
|
||||
case mullvad:
|
||||
formatted = currentServers.Mullvad.ToMarkdown()
|
||||
case nordvpn:
|
||||
formatted = currentServers.Nordvpn.ToMarkdown()
|
||||
case perfectPrivacy:
|
||||
formatted = currentServers.Perfectprivacy.ToMarkdown()
|
||||
case pia:
|
||||
formatted = currentServers.Pia.ToMarkdown()
|
||||
case privado:
|
||||
formatted = currentServers.Privado.ToMarkdown()
|
||||
case privatevpn:
|
||||
formatted = currentServers.Privatevpn.ToMarkdown()
|
||||
case protonvpn:
|
||||
formatted = currentServers.Protonvpn.ToMarkdown()
|
||||
case purevpn:
|
||||
formatted = currentServers.Purevpn.ToMarkdown()
|
||||
case surfshark:
|
||||
formatted = currentServers.Surfshark.ToMarkdown()
|
||||
case torguard:
|
||||
formatted = currentServers.Torguard.ToMarkdown()
|
||||
case vpnUnlimited:
|
||||
formatted = currentServers.VPNUnlimited.ToMarkdown()
|
||||
case vyprvpn:
|
||||
formatted = currentServers.Vyprvpn.ToMarkdown()
|
||||
case wevpn:
|
||||
formatted = currentServers.Wevpn.ToMarkdown()
|
||||
case windscribe:
|
||||
formatted = currentServers.Windscribe.ToMarkdown()
|
||||
default:
|
||||
return ErrProviderUnspecified
|
||||
}
|
||||
formatted := storage.FormatToMarkdown(providerToFormat)
|
||||
|
||||
output = filepath.Clean(output)
|
||||
file, err := os.OpenFile(output, os.O_TRUNC|os.O_WRONLY|os.O_CREATE, 0644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %s", ErrOpenOutputFile, err)
|
||||
return fmt.Errorf("opening output file: %w", err)
|
||||
}
|
||||
|
||||
_, err = fmt.Fprint(file, formatted)
|
||||
if err != nil {
|
||||
_ = file.Close()
|
||||
return fmt.Errorf("%w: %s", ErrWriteOutput, err)
|
||||
return fmt.Errorf("writing to output file: %w", err)
|
||||
}
|
||||
|
||||
err = file.Close()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %s", ErrCloseOutputFile, err)
|
||||
return fmt.Errorf("closing output file: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -6,21 +6,18 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/sources"
|
||||
"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 Source, warner Warner) error {
|
||||
// Extract the health server port from the configuration.
|
||||
config, err := source.ReadHealth()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
config.SetDefaults()
|
||||
|
||||
err = config.Validate()
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
package sources
|
||||
package cli
|
||||
|
||||
import "github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
|
||||
type Source interface {
|
||||
Read() (settings settings.Settings, err error)
|
||||
ReadHealth() (settings settings.Health, err error)
|
||||
ReadHealth() (health settings.Health, err error)
|
||||
String() string
|
||||
}
|
||||
@@ -1,50 +1,84 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/sources"
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/openvpn/extract"
|
||||
"github.com/qdm12/gluetun/internal/provider"
|
||||
"github.com/qdm12/gluetun/internal/publicip/ipinfo"
|
||||
"github.com/qdm12/gluetun/internal/storage"
|
||||
"github.com/qdm12/gluetun/internal/updater/resolver"
|
||||
)
|
||||
|
||||
type OpenvpnConfigMaker interface {
|
||||
OpenvpnConfig(logger OpenvpnConfigLogger, source sources.Source) error
|
||||
}
|
||||
|
||||
type OpenvpnConfigLogger interface {
|
||||
Info(s string)
|
||||
Warn(s string)
|
||||
}
|
||||
|
||||
func (c *CLI) OpenvpnConfig(logger OpenvpnConfigLogger, source sources.Source) error {
|
||||
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)
|
||||
}
|
||||
|
||||
type IPv6Checker interface {
|
||||
IsIPv6Supported() (supported bool, err error)
|
||||
}
|
||||
|
||||
func (c *CLI) OpenvpnConfig(logger OpenvpnConfigLogger, source Source,
|
||||
ipv6Checker IPv6Checker) error {
|
||||
storage, err := storage.New(logger, constants.ServersData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
allServers := storage.GetServers()
|
||||
|
||||
allSettings, err := source.Read()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = allSettings.Validate(allServers); err != nil {
|
||||
ipv6Supported, err := ipv6Checker.IsIPv6Supported()
|
||||
if err != nil {
|
||||
return fmt.Errorf("checking for IPv6 support: %w", err)
|
||||
}
|
||||
|
||||
if err = allSettings.Validate(storage, ipv6Supported); err != nil {
|
||||
return fmt.Errorf("validating settings: %w", err)
|
||||
}
|
||||
|
||||
// Unused by this CLI command
|
||||
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, ipv6Supported)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
providerConf := provider.New(*allSettings.VPN.Provider.Name, allServers, time.Now)
|
||||
connection, err := providerConf.GetConnection(allSettings.VPN.Provider.ServerSelection)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
lines, err := providerConf.BuildConf(connection, allSettings.VPN.OpenVPN)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
lines := providerConf.OpenVPNConfig(connection,
|
||||
allSettings.VPN.OpenVPN, ipv6Supported)
|
||||
|
||||
fmt.Println(strings.Join(lines, "\n"))
|
||||
return nil
|
||||
|
||||
@@ -2,53 +2,48 @@ package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||
"github.com/qdm12/gluetun/internal/openvpn/extract"
|
||||
"github.com/qdm12/gluetun/internal/provider"
|
||||
"github.com/qdm12/gluetun/internal/publicip/ipinfo"
|
||||
"github.com/qdm12/gluetun/internal/storage"
|
||||
"github.com/qdm12/gluetun/internal/updater"
|
||||
"github.com/qdm12/gluetun/internal/updater/resolver"
|
||||
"github.com/qdm12/gluetun/internal/updater/unzip"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrModeUnspecified = errors.New("at least one of -enduser or -maintainer must be 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")
|
||||
ErrModeUnspecified = errors.New("at least one of -enduser or -maintainer must be specified")
|
||||
ErrNoProviderSpecified = errors.New("no provider was specified")
|
||||
)
|
||||
|
||||
type Updater interface {
|
||||
Update(ctx context.Context, args []string, logger UpdaterLogger) error
|
||||
}
|
||||
|
||||
type UpdaterLogger interface {
|
||||
Info(s string)
|
||||
Warn(s string)
|
||||
Error(s string)
|
||||
}
|
||||
|
||||
func boolPtr(b bool) *bool { return &b }
|
||||
|
||||
func (c *CLI) Update(ctx context.Context, args []string, logger UpdaterLogger) error {
|
||||
options := settings.Updater{CLI: boolPtr(true)}
|
||||
options := settings.Updater{}
|
||||
var endUserMode, maintainerMode, updateAll bool
|
||||
var dnsAddress, csvProviders string
|
||||
var csvProviders string
|
||||
flagSet := flag.NewFlagSet("update", flag.ExitOnError)
|
||||
flagSet.BoolVar(&endUserMode, "enduser", false, "Write results to /gluetun/servers.json (for end users)")
|
||||
flagSet.BoolVar(&maintainerMode, "maintainer", false,
|
||||
"Write results to ./internal/storage/servers.json to modify the program (for maintainers)")
|
||||
flagSet.StringVar(&dnsAddress, "dns", "8.8.8.8", "DNS resolver address to use")
|
||||
flagSet.StringVar(&options.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.StringVar(&csvProviders, "providers", "", "CSV string of VPN providers to update server data for")
|
||||
if err := flagSet.Parse(args); err != nil {
|
||||
@@ -59,18 +54,8 @@ func (c *CLI) Update(ctx context.Context, args []string, logger UpdaterLogger) e
|
||||
return ErrModeUnspecified
|
||||
}
|
||||
|
||||
options.DNSAddress = net.ParseIP(dnsAddress)
|
||||
if options.DNSAddress == nil {
|
||||
return fmt.Errorf("%w: %s", ErrDNSAddress, dnsAddress)
|
||||
}
|
||||
|
||||
if updateAll {
|
||||
for _, provider := range constants.AllProviders() {
|
||||
if provider == constants.Custom {
|
||||
continue
|
||||
}
|
||||
options.Providers = append(options.Providers, provider)
|
||||
}
|
||||
options.Providers = providers.All()
|
||||
} else {
|
||||
if csvProviders == "" {
|
||||
return ErrNoProviderSpecified
|
||||
@@ -78,55 +63,40 @@ func (c *CLI) Update(ctx context.Context, args []string, logger UpdaterLogger) e
|
||||
options.Providers = strings.Split(csvProviders, ",")
|
||||
}
|
||||
|
||||
options.SetDefaults()
|
||||
options.SetDefaults(options.Providers[0])
|
||||
|
||||
err := options.Validate()
|
||||
if err != nil {
|
||||
return fmt.Errorf("options validation failed: %w", err)
|
||||
}
|
||||
|
||||
const clientTimeout = 10 * time.Second
|
||||
httpClient := &http.Client{Timeout: clientTimeout}
|
||||
|
||||
storage, err := storage.New(logger, constants.ServersData)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %s", ErrNewStorage, err)
|
||||
return fmt.Errorf("creating servers storage: %w", err)
|
||||
}
|
||||
currentServers := storage.GetServers()
|
||||
|
||||
updater := updater.New(options, httpClient, currentServers, logger)
|
||||
allServers, err := updater.UpdateServers(ctx)
|
||||
const clientTimeout = 10 * time.Second
|
||||
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,
|
||||
unzipper, parallelResolver, ipFetcher, openvpnFileExtractor)
|
||||
|
||||
updater := updater.New(httpClient, storage, providers, logger)
|
||||
err = updater.UpdateServers(ctx, options.Providers, options.MinRatio)
|
||||
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)
|
||||
}
|
||||
return fmt.Errorf("updating server information: %w", err)
|
||||
}
|
||||
|
||||
if maintainerMode {
|
||||
if err := writeToEmbeddedJSON(c.repoServersPath, allServers); err != nil {
|
||||
return fmt.Errorf("%w: %s", ErrWriteToFile, err)
|
||||
err := storage.FlushToFile(c.repoServersPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("writing servers data to embedded JSON file: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ type DNS struct {
|
||||
func (d DNS) validate() (err error) {
|
||||
err = d.DoT.validate()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed validating DoT settings: %w", err)
|
||||
return fmt.Errorf("validating DoT settings: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -65,7 +65,7 @@ func (d *DoT) copy() (copied DoT) {
|
||||
// unset field of the receiver settings object.
|
||||
func (d *DoT) mergeWith(other DoT) {
|
||||
d.Enabled = helpers.MergeWithBool(d.Enabled, other.Enabled)
|
||||
d.UpdatePeriod = helpers.MergeWithDuration(d.UpdatePeriod, other.UpdatePeriod)
|
||||
d.UpdatePeriod = helpers.MergeWithDurationPtr(d.UpdatePeriod, other.UpdatePeriod)
|
||||
d.Unbound.mergeWith(other.Unbound)
|
||||
d.Blacklist.mergeWith(other.Blacklist)
|
||||
}
|
||||
@@ -75,7 +75,7 @@ func (d *DoT) mergeWith(other DoT) {
|
||||
// settings.
|
||||
func (d *DoT) overrideWith(other DoT) {
|
||||
d.Enabled = helpers.OverrideWithBool(d.Enabled, other.Enabled)
|
||||
d.UpdatePeriod = helpers.OverrideWithDuration(d.UpdatePeriod, other.UpdatePeriod)
|
||||
d.UpdatePeriod = helpers.OverrideWithDurationPtr(d.UpdatePeriod, other.UpdatePeriod)
|
||||
d.Unbound.overrideWith(other.Unbound)
|
||||
d.Blacklist.overrideWith(other.Blacklist)
|
||||
}
|
||||
@@ -83,7 +83,7 @@ func (d *DoT) overrideWith(other DoT) {
|
||||
func (d *DoT) setDefaults() {
|
||||
d.Enabled = helpers.DefaultBool(d.Enabled, true)
|
||||
const defaultUpdatePeriod = 24 * time.Hour
|
||||
d.UpdatePeriod = helpers.DefaultDuration(d.UpdatePeriod, defaultUpdatePeriod)
|
||||
d.UpdatePeriod = helpers.DefaultDurationPtr(d.UpdatePeriod, defaultUpdatePeriod)
|
||||
d.Unbound.setDefaults()
|
||||
d.Blacklist.setDefaults()
|
||||
}
|
||||
|
||||
@@ -6,18 +6,18 @@ var (
|
||||
ErrCityNotValid = errors.New("the city specified is not valid")
|
||||
ErrControlServerPrivilegedPort = errors.New("cannot use privileged port without running as root")
|
||||
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")
|
||||
ErrHostnameNotValid = errors.New("the hostname specified is not valid")
|
||||
ErrISPNotValid = errors.New("the ISP specified is not valid")
|
||||
ErrMinRatioNotValid = errors.New("minimum ratio is not valid")
|
||||
ErrMissingValue = errors.New("missing value")
|
||||
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")
|
||||
ErrOpenVPNClientKeyNotValid = errors.New("client key is not valid")
|
||||
ErrOpenVPNConfigFile = errors.New("custom configuration file error")
|
||||
ErrOpenVPNCustomPortNotAllowed = errors.New("custom endpoint port is not allowed")
|
||||
ErrOpenVPNEncryptionPresetNotValid = errors.New("PIA encryption preset is not valid")
|
||||
ErrOpenVPNInterfaceNotValid = errors.New("interface name is not valid")
|
||||
ErrOpenVPNKeyPassphraseIsEmpty = errors.New("key passphrase is empty")
|
||||
ErrOpenVPNMSSFixIsTooHigh = errors.New("mssfix option value is too high")
|
||||
ErrOpenVPNPasswordIsEmpty = errors.New("password is empty")
|
||||
ErrOpenVPNTCPNotSupported = errors.New("TCP protocol is not supported")
|
||||
@@ -25,27 +25,25 @@ var (
|
||||
ErrOpenVPNVerbosityIsOutOfBounds = errors.New("verbosity value is out of bounds")
|
||||
ErrOpenVPNVersionIsNotValid = errors.New("version is not valid")
|
||||
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")
|
||||
ErrRegionNotValid = errors.New("the region specified is not valid")
|
||||
ErrServerAddressNotValid = errors.New("server listening address is not valid")
|
||||
ErrSystemPGIDNotValid = errors.New("process group id is not valid")
|
||||
ErrSystemPUIDNotValid = errors.New("process user id 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")
|
||||
ErrVPNTypeNotValid = errors.New("VPN type is not valid")
|
||||
ErrWireguardEndpointIPNotSet = errors.New("endpoint IP is not set")
|
||||
ErrWireguardEndpointPortNotAllowed = errors.New("endpoint port is not allowed")
|
||||
ErrWireguardEndpointPortNotSet = errors.New("endpoint port is not set")
|
||||
ErrWireguardEndpointPortSet = errors.New("endpoint port is set")
|
||||
ErrWireguardInterfaceAddressNotSet = errors.New("interface address is not set")
|
||||
ErrWireguardInterfaceAddressIPv6 = errors.New("interface address is IPv6 but IPv6 is not supported")
|
||||
ErrWireguardInterfaceNotValid = errors.New("interface name is not valid")
|
||||
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")
|
||||
ErrWireguardPrivateKeyNotValid = errors.New("private key is not valid")
|
||||
ErrWireguardPublicKeyNotSet = errors.New("public key is not set")
|
||||
ErrWireguardPublicKeyNotValid = errors.New("public key is not valid")
|
||||
|
||||
ErrUpdaterPeriodTooSmall = errors.New("VPN server data updater period is too small")
|
||||
ErrWireguardImplementationNotValid = errors.New("implementation is not valid")
|
||||
)
|
||||
|
||||
@@ -109,7 +109,8 @@ func (f Firewall) toLinesNode() (node *gotree.Node) {
|
||||
if len(f.OutboundSubnets) > 0 {
|
||||
outboundSubnets := node.Appendf("Outbound subnets:")
|
||||
for _, subnet := range f.OutboundSubnets {
|
||||
outboundSubnets.Appendf("%s", subnet)
|
||||
subnet := subnet
|
||||
outboundSubnets.Appendf("%s", &subnet)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package settings
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||
"github.com/qdm12/gotree"
|
||||
@@ -15,6 +16,12 @@ type Health struct {
|
||||
// for the health check server.
|
||||
// It cannot be the empty string in the internal state.
|
||||
ServerAddress string
|
||||
// ReadHeaderTimeout is the HTTP server header read timeout
|
||||
// duration of the HTTP server. It defaults to 100 milliseconds.
|
||||
ReadHeaderTimeout time.Duration
|
||||
// ReadTimeout is the HTTP read timeout duration of the
|
||||
// HTTP server. It defaults to 500 milliseconds.
|
||||
ReadTimeout time.Duration
|
||||
// TargetAddress is the address (host or host:port)
|
||||
// to TCP dial to periodically for the health check.
|
||||
// It cannot be the empty string in the internal state.
|
||||
@@ -27,13 +34,12 @@ func (h Health) Validate() (err error) {
|
||||
_, err = address.Validate(h.ServerAddress,
|
||||
address.OptionListening(uid))
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %s",
|
||||
ErrServerAddressNotValid, err)
|
||||
return fmt.Errorf("server listening address is not valid: %w", err)
|
||||
}
|
||||
|
||||
err = h.VPN.validate()
|
||||
if err != nil {
|
||||
return fmt.Errorf("health VPN settings validation failed: %w", err)
|
||||
return fmt.Errorf("health VPN settings: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -41,9 +47,11 @@ func (h Health) Validate() (err error) {
|
||||
|
||||
func (h *Health) copy() (copied Health) {
|
||||
return Health{
|
||||
ServerAddress: h.ServerAddress,
|
||||
TargetAddress: h.TargetAddress,
|
||||
VPN: h.VPN.copy(),
|
||||
ServerAddress: h.ServerAddress,
|
||||
ReadHeaderTimeout: h.ReadHeaderTimeout,
|
||||
ReadTimeout: h.ReadTimeout,
|
||||
TargetAddress: h.TargetAddress,
|
||||
VPN: h.VPN.copy(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,6 +59,8 @@ func (h *Health) copy() (copied Health) {
|
||||
// unset field of the receiver settings object.
|
||||
func (h *Health) MergeWith(other Health) {
|
||||
h.ServerAddress = helpers.MergeWithString(h.ServerAddress, other.ServerAddress)
|
||||
h.ReadHeaderTimeout = helpers.MergeWithDuration(h.ReadHeaderTimeout, other.ReadHeaderTimeout)
|
||||
h.ReadTimeout = helpers.MergeWithDuration(h.ReadTimeout, other.ReadTimeout)
|
||||
h.TargetAddress = helpers.MergeWithString(h.TargetAddress, other.TargetAddress)
|
||||
h.VPN.mergeWith(other.VPN)
|
||||
}
|
||||
@@ -60,13 +70,19 @@ func (h *Health) MergeWith(other Health) {
|
||||
// settings.
|
||||
func (h *Health) OverrideWith(other Health) {
|
||||
h.ServerAddress = helpers.OverrideWithString(h.ServerAddress, other.ServerAddress)
|
||||
h.ReadHeaderTimeout = helpers.OverrideWithDuration(h.ReadHeaderTimeout, other.ReadHeaderTimeout)
|
||||
h.ReadTimeout = helpers.OverrideWithDuration(h.ReadTimeout, other.ReadTimeout)
|
||||
h.TargetAddress = helpers.OverrideWithString(h.TargetAddress, other.TargetAddress)
|
||||
h.VPN.overrideWith(other.VPN)
|
||||
}
|
||||
|
||||
func (h *Health) SetDefaults() {
|
||||
h.ServerAddress = helpers.DefaultString(h.ServerAddress, "127.0.0.1:9999")
|
||||
h.TargetAddress = helpers.DefaultString(h.TargetAddress, "github.com:443")
|
||||
const defaultReadHeaderTimeout = 100 * time.Millisecond
|
||||
h.ReadHeaderTimeout = helpers.DefaultDuration(h.ReadHeaderTimeout, defaultReadHeaderTimeout)
|
||||
const defaultReadTimeout = 500 * time.Millisecond
|
||||
h.ReadTimeout = helpers.DefaultDuration(h.ReadTimeout, defaultReadTimeout)
|
||||
h.TargetAddress = helpers.DefaultString(h.TargetAddress, "cloudflare.com:443")
|
||||
h.VPN.setDefaults()
|
||||
}
|
||||
|
||||
@@ -78,6 +94,8 @@ func (h Health) toLinesNode() (node *gotree.Node) {
|
||||
node = gotree.New("Health settings:")
|
||||
node.Appendf("Server listening address: %s", h.ServerAddress)
|
||||
node.Appendf("Target address: %s", h.TargetAddress)
|
||||
node.Appendf("Read header timeout: %s", h.ReadHeaderTimeout)
|
||||
node.Appendf("Read timeout: %s", h.ReadTimeout)
|
||||
node.AppendNode(h.VPN.toLinesNode("VPN"))
|
||||
return node
|
||||
}
|
||||
|
||||
@@ -35,23 +35,23 @@ func (h *HealthyWait) copy() (copied HealthyWait) {
|
||||
// mergeWith merges the other settings into any
|
||||
// unset field of the receiver settings object.
|
||||
func (h *HealthyWait) mergeWith(other HealthyWait) {
|
||||
h.Initial = helpers.MergeWithDuration(h.Initial, other.Initial)
|
||||
h.Addition = helpers.MergeWithDuration(h.Addition, other.Addition)
|
||||
h.Initial = helpers.MergeWithDurationPtr(h.Initial, other.Initial)
|
||||
h.Addition = helpers.MergeWithDurationPtr(h.Addition, other.Addition)
|
||||
}
|
||||
|
||||
// overrideWith overrides fields of the receiver
|
||||
// settings object with any field set in the other
|
||||
// settings.
|
||||
func (h *HealthyWait) overrideWith(other HealthyWait) {
|
||||
h.Initial = helpers.OverrideWithDuration(h.Initial, other.Initial)
|
||||
h.Addition = helpers.OverrideWithDuration(h.Addition, other.Addition)
|
||||
h.Initial = helpers.OverrideWithDurationPtr(h.Initial, other.Initial)
|
||||
h.Addition = helpers.OverrideWithDurationPtr(h.Addition, other.Addition)
|
||||
}
|
||||
|
||||
func (h *HealthyWait) setDefaults() {
|
||||
const initialDurationDefault = 6 * time.Second
|
||||
const additionDurationDefault = 5 * time.Second
|
||||
h.Initial = helpers.DefaultDuration(h.Initial, initialDurationDefault)
|
||||
h.Addition = helpers.DefaultDuration(h.Addition, additionDurationDefault)
|
||||
h.Initial = helpers.DefaultDurationPtr(h.Initial, initialDurationDefault)
|
||||
h.Addition = helpers.DefaultDurationPtr(h.Addition, additionDurationDefault)
|
||||
}
|
||||
|
||||
func (h HealthyWait) String() string {
|
||||
|
||||
@@ -15,9 +15,16 @@ func IsOneOf(value string, choices ...string) (ok bool) {
|
||||
return false
|
||||
}
|
||||
|
||||
var ErrValueNotOneOf = errors.New("value is not one of the possible choices")
|
||||
var (
|
||||
ErrNoChoice = errors.New("one or more values is set but there is no possible value available")
|
||||
ErrValueNotOneOf = errors.New("value is not one of the possible choices")
|
||||
)
|
||||
|
||||
func AreAllOneOf(values, choices []string) (err error) {
|
||||
if len(values) > 0 && len(choices) == 0 {
|
||||
return ErrNoChoice
|
||||
}
|
||||
|
||||
set := make(map[string]struct{}, len(choices))
|
||||
for _, choice := range choices {
|
||||
choice = strings.ToLower(choice)
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/qdm12/golibs/logging"
|
||||
"github.com/qdm12/log"
|
||||
"inet.af/netaddr"
|
||||
)
|
||||
|
||||
@@ -44,6 +44,15 @@ func CopyUint16Ptr(original *uint16) (copied *uint16) {
|
||||
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) {
|
||||
if original == nil {
|
||||
return nil
|
||||
@@ -62,11 +71,11 @@ func CopyDurationPtr(original *time.Duration) (copied *time.Duration) {
|
||||
return copied
|
||||
}
|
||||
|
||||
func CopyLogLevelPtr(original *logging.Level) (copied *logging.Level) {
|
||||
func CopyLogLevelPtr(original *log.Level) (copied *log.Level) {
|
||||
if original == nil {
|
||||
return nil
|
||||
}
|
||||
copied = new(logging.Level)
|
||||
copied = new(log.Level)
|
||||
*copied = *original
|
||||
return copied
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/qdm12/golibs/logging"
|
||||
"github.com/qdm12/log"
|
||||
)
|
||||
|
||||
func DefaultInt(existing *int, defaultValue int) (
|
||||
@@ -36,6 +36,15 @@ func DefaultUint16(existing *uint16, defaultValue uint16) (
|
||||
*result = defaultValue
|
||||
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) (
|
||||
result *bool) {
|
||||
@@ -64,7 +73,15 @@ func DefaultStringPtr(existing *string, defaultValue string) (result *string) {
|
||||
return result
|
||||
}
|
||||
|
||||
func DefaultDuration(existing *time.Duration,
|
||||
func DefaultDuration(existing time.Duration,
|
||||
defaultValue time.Duration) (result time.Duration) {
|
||||
if existing != 0 {
|
||||
return existing
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
func DefaultDurationPtr(existing *time.Duration,
|
||||
defaultValue time.Duration) (result *time.Duration) {
|
||||
if existing != nil {
|
||||
return existing
|
||||
@@ -74,12 +91,12 @@ func DefaultDuration(existing *time.Duration,
|
||||
return result
|
||||
}
|
||||
|
||||
func DefaultLogLevel(existing *logging.Level,
|
||||
defaultValue logging.Level) (result *logging.Level) {
|
||||
func DefaultLogLevel(existing *log.Level,
|
||||
defaultValue log.Level) (result *log.Level) {
|
||||
if existing != nil {
|
||||
return existing
|
||||
}
|
||||
result = new(logging.Level)
|
||||
result = new(log.Level)
|
||||
*result = defaultValue
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -2,9 +2,10 @@ package helpers
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/qdm12/golibs/logging"
|
||||
"github.com/qdm12/log"
|
||||
"inet.af/netaddr"
|
||||
)
|
||||
|
||||
@@ -26,6 +27,20 @@ func MergeWithString(existing, other string) (result string) {
|
||||
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) {
|
||||
if existing != nil {
|
||||
return existing
|
||||
@@ -37,7 +52,7 @@ func MergeWithStringPtr(existing, other *string) (result *string) {
|
||||
return result
|
||||
}
|
||||
|
||||
func MergeWithInt(existing, other *int) (result *int) {
|
||||
func MergeWithIntPtr(existing, other *int) (result *int) {
|
||||
if existing != nil {
|
||||
return existing
|
||||
} else if other == nil {
|
||||
@@ -70,6 +85,17 @@ func MergeWithUint16(existing, other *uint16) (result *uint16) {
|
||||
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) {
|
||||
if existing != nil {
|
||||
return existing
|
||||
@@ -81,24 +107,38 @@ func MergeWithIP(existing, other net.IP) (result net.IP) {
|
||||
return result
|
||||
}
|
||||
|
||||
func MergeWithDuration(existing, other *time.Duration) (result *time.Duration) {
|
||||
func MergeWithDuration(existing, other time.Duration) (result time.Duration) {
|
||||
if existing != 0 {
|
||||
return existing
|
||||
}
|
||||
return other
|
||||
}
|
||||
|
||||
func MergeWithDurationPtr(existing, other *time.Duration) (result *time.Duration) {
|
||||
if existing != nil {
|
||||
return existing
|
||||
}
|
||||
return other
|
||||
}
|
||||
|
||||
func MergeWithLogLevel(existing, other *logging.Level) (result *logging.Level) {
|
||||
func MergeWithLogLevel(existing, other *log.Level) (result *log.Level) {
|
||||
if existing != nil {
|
||||
return existing
|
||||
} else if other == nil {
|
||||
return nil
|
||||
}
|
||||
result = new(logging.Level)
|
||||
result = new(log.Level)
|
||||
*result = *other
|
||||
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) {
|
||||
if a == nil && b == nil {
|
||||
return nil
|
||||
|
||||
@@ -2,9 +2,10 @@ package helpers
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/qdm12/golibs/logging"
|
||||
"github.com/qdm12/log"
|
||||
"inet.af/netaddr"
|
||||
)
|
||||
|
||||
@@ -24,6 +25,20 @@ func OverrideWithString(existing, other string) (result string) {
|
||||
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) {
|
||||
if other == nil {
|
||||
return existing
|
||||
@@ -33,7 +48,7 @@ func OverrideWithStringPtr(existing, other *string) (result *string) {
|
||||
return result
|
||||
}
|
||||
|
||||
func OverrideWithInt(existing, other *int) (result *int) {
|
||||
func OverrideWithIntPtr(existing, other *int) (result *int) {
|
||||
if other == nil {
|
||||
return existing
|
||||
}
|
||||
@@ -60,6 +75,15 @@ func OverrideWithUint16(existing, other *uint16) (result *uint16) {
|
||||
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) {
|
||||
if other == nil {
|
||||
return existing
|
||||
@@ -69,7 +93,16 @@ func OverrideWithIP(existing, other net.IP) (result net.IP) {
|
||||
return result
|
||||
}
|
||||
|
||||
func OverrideWithDuration(existing, other *time.Duration) (result *time.Duration) {
|
||||
func OverrideWithDuration(existing, other time.Duration) (
|
||||
result time.Duration) {
|
||||
if other == 0 {
|
||||
return existing
|
||||
}
|
||||
return other
|
||||
}
|
||||
|
||||
func OverrideWithDurationPtr(existing, other *time.Duration) (
|
||||
result *time.Duration) {
|
||||
if other == nil {
|
||||
return existing
|
||||
}
|
||||
@@ -78,15 +111,22 @@ func OverrideWithDuration(existing, other *time.Duration) (result *time.Duration
|
||||
return result
|
||||
}
|
||||
|
||||
func OverrideWithLogLevel(existing, other *logging.Level) (result *logging.Level) {
|
||||
func OverrideWithLogLevel(existing, other *log.Level) (result *log.Level) {
|
||||
if other == nil {
|
||||
return existing
|
||||
}
|
||||
result = new(logging.Level)
|
||||
result = new(log.Level)
|
||||
*result = *other
|
||||
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) {
|
||||
if other == nil {
|
||||
return existing
|
||||
|
||||
@@ -3,6 +3,7 @@ package settings
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||
"github.com/qdm12/gotree"
|
||||
@@ -33,6 +34,12 @@ type HTTPProxy struct {
|
||||
// each request/response. It cannot be nil in the
|
||||
// internal state.
|
||||
Log *bool
|
||||
// ReadHeaderTimeout is the HTTP header read timeout duration
|
||||
// of the HTTP server. It defaults to 1 second if left unset.
|
||||
ReadHeaderTimeout time.Duration
|
||||
// ReadTimeout is the HTTP read timeout duration
|
||||
// of the HTTP server. It defaults to 3 seconds if left unset.
|
||||
ReadTimeout time.Duration
|
||||
}
|
||||
|
||||
func (h HTTPProxy) validate() (err error) {
|
||||
@@ -41,8 +48,7 @@ func (h HTTPProxy) validate() (err error) {
|
||||
uid := os.Getuid()
|
||||
_, err = address.Validate(h.ListeningAddress, address.OptionListening(uid))
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %s",
|
||||
ErrServerAddressNotValid, h.ListeningAddress)
|
||||
return fmt.Errorf("%w: %s", ErrServerAddressNotValid, h.ListeningAddress)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -50,12 +56,14 @@ func (h HTTPProxy) validate() (err error) {
|
||||
|
||||
func (h *HTTPProxy) copy() (copied HTTPProxy) {
|
||||
return HTTPProxy{
|
||||
User: helpers.CopyStringPtr(h.User),
|
||||
Password: helpers.CopyStringPtr(h.Password),
|
||||
ListeningAddress: h.ListeningAddress,
|
||||
Enabled: helpers.CopyBoolPtr(h.Enabled),
|
||||
Stealth: helpers.CopyBoolPtr(h.Stealth),
|
||||
Log: helpers.CopyBoolPtr(h.Log),
|
||||
User: helpers.CopyStringPtr(h.User),
|
||||
Password: helpers.CopyStringPtr(h.Password),
|
||||
ListeningAddress: h.ListeningAddress,
|
||||
Enabled: helpers.CopyBoolPtr(h.Enabled),
|
||||
Stealth: helpers.CopyBoolPtr(h.Stealth),
|
||||
Log: helpers.CopyBoolPtr(h.Log),
|
||||
ReadHeaderTimeout: h.ReadHeaderTimeout,
|
||||
ReadTimeout: h.ReadTimeout,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,6 +76,8 @@ func (h *HTTPProxy) mergeWith(other HTTPProxy) {
|
||||
h.Enabled = helpers.MergeWithBool(h.Enabled, other.Enabled)
|
||||
h.Stealth = helpers.MergeWithBool(h.Stealth, other.Stealth)
|
||||
h.Log = helpers.MergeWithBool(h.Log, other.Log)
|
||||
h.ReadHeaderTimeout = helpers.MergeWithDuration(h.ReadHeaderTimeout, other.ReadHeaderTimeout)
|
||||
h.ReadTimeout = helpers.MergeWithDuration(h.ReadTimeout, other.ReadTimeout)
|
||||
}
|
||||
|
||||
// overrideWith overrides fields of the receiver
|
||||
@@ -80,6 +90,8 @@ func (h *HTTPProxy) overrideWith(other HTTPProxy) {
|
||||
h.Enabled = helpers.OverrideWithBool(h.Enabled, other.Enabled)
|
||||
h.Stealth = helpers.OverrideWithBool(h.Stealth, other.Stealth)
|
||||
h.Log = helpers.OverrideWithBool(h.Log, other.Log)
|
||||
h.ReadHeaderTimeout = helpers.OverrideWithDuration(h.ReadHeaderTimeout, other.ReadHeaderTimeout)
|
||||
h.ReadTimeout = helpers.OverrideWithDuration(h.ReadTimeout, other.ReadTimeout)
|
||||
}
|
||||
|
||||
func (h *HTTPProxy) setDefaults() {
|
||||
@@ -89,6 +101,10 @@ func (h *HTTPProxy) setDefaults() {
|
||||
h.Enabled = helpers.DefaultBool(h.Enabled, false)
|
||||
h.Stealth = helpers.DefaultBool(h.Stealth, false)
|
||||
h.Log = helpers.DefaultBool(h.Log, false)
|
||||
const defaultReadHeaderTimeout = time.Second
|
||||
h.ReadHeaderTimeout = helpers.DefaultDuration(h.ReadHeaderTimeout, defaultReadHeaderTimeout)
|
||||
const defaultReadTimeout = 3 * time.Second
|
||||
h.ReadTimeout = helpers.DefaultDuration(h.ReadTimeout, defaultReadTimeout)
|
||||
}
|
||||
|
||||
func (h HTTPProxy) String() string {
|
||||
@@ -107,6 +123,8 @@ func (h HTTPProxy) toLinesNode() (node *gotree.Node) {
|
||||
node.Appendf("Password: %s", helpers.ObfuscatePassword(*h.Password))
|
||||
node.Appendf("Stealth mode: %s", helpers.BoolPtrToYesNo(h.Stealth))
|
||||
node.Appendf("Log: %s", helpers.BoolPtrToYesNo(h.Log))
|
||||
node.Appendf("Read header timeout: %s", h.ReadHeaderTimeout)
|
||||
node.Appendf("Read timeout: %s", h.ReadTimeout)
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
@@ -2,15 +2,15 @@ package settings
|
||||
|
||||
import (
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||
"github.com/qdm12/golibs/logging"
|
||||
"github.com/qdm12/gotree"
|
||||
"github.com/qdm12/log"
|
||||
)
|
||||
|
||||
// Log contains settings to configure the logger.
|
||||
type Log struct {
|
||||
// Level is the log level of the logger.
|
||||
// It cannot be nil in the internal state.
|
||||
Level *logging.Level
|
||||
Level *log.Level
|
||||
}
|
||||
|
||||
func (l Log) validate() (err error) {
|
||||
@@ -37,7 +37,7 @@ func (l *Log) overrideWith(other Log) {
|
||||
}
|
||||
|
||||
func (l *Log) setDefaults() {
|
||||
l.Level = helpers.DefaultLogLevel(l.Level, logging.LevelInfo)
|
||||
l.Level = helpers.DefaultLogLevel(l.Level, log.LevelInfo)
|
||||
}
|
||||
|
||||
func (l Log) String() string {
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
package settings
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/openvpn/parse"
|
||||
"github.com/qdm12/gluetun/internal/constants/openvpn"
|
||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||
"github.com/qdm12/gluetun/internal/openvpn/extract"
|
||||
"github.com/qdm12/gluetun/internal/provider/privateinternetaccess/presets"
|
||||
"github.com/qdm12/gotree"
|
||||
)
|
||||
|
||||
@@ -16,13 +20,15 @@ type OpenVPN struct {
|
||||
// It can only be "2.4" or "2.5".
|
||||
Version string
|
||||
// User is the OpenVPN authentication username.
|
||||
// It cannot be an empty string in the internal state
|
||||
// if OpenVPN is used.
|
||||
User string
|
||||
// It cannot be nil in the internal state if OpenVPN is used.
|
||||
// It is usually required but in some cases can be the empty string
|
||||
// to indicate no user+password authentication is needed.
|
||||
User *string
|
||||
// Password is the OpenVPN authentication password.
|
||||
// It cannot be an empty string in the internal state
|
||||
// if OpenVPN is used.
|
||||
Password string
|
||||
// It cannot be nil in the internal state if OpenVPN is used.
|
||||
// It is usually required but in some cases can be the empty string
|
||||
// to indicate no user+password authentication is needed.
|
||||
Password *string
|
||||
// ConfFile is a custom OpenVPN configuration file path.
|
||||
// It can be set to the empty string for it to be ignored.
|
||||
// It cannot be nil in the internal state.
|
||||
@@ -36,24 +42,29 @@ type OpenVPN struct {
|
||||
// It cannot be nil in the internal state.
|
||||
// It is ignored if it is set to the empty string.
|
||||
Auth *string
|
||||
// ClientCrt is the OpenVPN client certificate.
|
||||
// This is notably used by Cyberghost.
|
||||
// Cert is the base64 encoded DER of an OpenVPN certificate for the <cert> block.
|
||||
// This is notably used by Cyberghost and VPN secure.
|
||||
// It can be set to the empty string to be ignored.
|
||||
// It cannot be nil in the internal state.
|
||||
ClientCrt *string
|
||||
// ClientKey is the OpenVPN client key.
|
||||
Cert *string
|
||||
// Key is the base64 encoded DER of an OpenVPN key.
|
||||
// This is used by Cyberghost and VPN Unlimited.
|
||||
// It can be set to the empty string to be ignored.
|
||||
// It cannot be nil in the internal state.
|
||||
ClientKey *string
|
||||
Key *string
|
||||
// EncryptedKey is the base64 encoded DER of an encrypted key for OpenVPN.
|
||||
// It is used by VPN secure.
|
||||
// It defaults to the empty string meaning it is not
|
||||
// to be used. KeyPassphrase must be set if this one is set.
|
||||
EncryptedKey *string
|
||||
// KeyPassphrase is the key passphrase to be used by OpenVPN
|
||||
// to decrypt the EncryptedPrivateKey. It defaults to the
|
||||
// empty string and must be set if EncryptedPrivateKey is set.
|
||||
KeyPassphrase *string
|
||||
// PIAEncPreset is the encryption preset for
|
||||
// Private Internet Access. It can be set to an
|
||||
// empty string for other providers.
|
||||
PIAEncPreset *string
|
||||
// IPv6 is set to true if IPv6 routing should be
|
||||
// set to be tunnel in OpenVPN, and false otherwise.
|
||||
// It cannot be nil in the internal state.
|
||||
IPv6 *bool // TODO automate like with Wireguard
|
||||
// MSSFix is the value (1 to 10000) to set for the
|
||||
// mssfix option for OpenVPN. It is ignored if set to 0.
|
||||
// It cannot be nil in the internal state.
|
||||
@@ -61,15 +72,10 @@ type OpenVPN struct {
|
||||
// Interface is the OpenVPN device interface name.
|
||||
// It cannot be an empty string in the internal state.
|
||||
Interface string
|
||||
// Root is true if OpenVPN is to be run as root,
|
||||
// and false otherwise. It cannot be nil in the
|
||||
// internal state.
|
||||
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
|
||||
// ProcessUser is the OpenVPN process OS username
|
||||
// to use. It cannot be empty in the internal state.
|
||||
// It defaults to 'root'.
|
||||
ProcessUser string
|
||||
// Verbosity is the OpenVPN verbosity level from 0 to 6.
|
||||
// It cannot be nil in the internal state.
|
||||
Verbosity *int
|
||||
@@ -78,69 +84,56 @@ type OpenVPN struct {
|
||||
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) {
|
||||
// Validate version
|
||||
validVersions := []string{constants.Openvpn24, constants.Openvpn25}
|
||||
validVersions := []string{openvpn.Openvpn24, openvpn.Openvpn25}
|
||||
if !helpers.IsOneOf(o.Version, validVersions...) {
|
||||
return fmt.Errorf("%w: %q can only be one of %s",
|
||||
ErrOpenVPNVersionIsNotValid, o.Version, strings.Join(validVersions, ", "))
|
||||
}
|
||||
|
||||
isCustom := vpnProvider == constants.Custom
|
||||
isCustom := vpnProvider == providers.Custom
|
||||
isUserRequired := !isCustom &&
|
||||
vpnProvider != providers.Airvpn &&
|
||||
vpnProvider != providers.VPNSecure
|
||||
|
||||
if !isCustom && o.User == "" {
|
||||
if isUserRequired && *o.User == "" {
|
||||
return ErrOpenVPNUserIsEmpty
|
||||
}
|
||||
|
||||
if !isCustom && o.Password == "" {
|
||||
passwordRequired := isUserRequired &&
|
||||
(vpnProvider != providers.Ivpn || !ivpnAccountID.MatchString(*o.User))
|
||||
|
||||
if passwordRequired && *o.Password == "" {
|
||||
return ErrOpenVPNPasswordIsEmpty
|
||||
}
|
||||
|
||||
// Validate ConfFile
|
||||
if isCustom {
|
||||
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 = validateOpenVPNConfigFilepath(isCustom, *o.ConfFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("custom configuration file: %w", err)
|
||||
}
|
||||
|
||||
// Check client certificate
|
||||
switch vpnProvider {
|
||||
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 = validateOpenVPNClientCertificate(vpnProvider, *o.Cert)
|
||||
if err != nil {
|
||||
return fmt.Errorf("client certificate: %w", err)
|
||||
}
|
||||
|
||||
// Check client key
|
||||
switch vpnProvider {
|
||||
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)
|
||||
}
|
||||
err = validateOpenVPNClientKey(vpnProvider, *o.Key)
|
||||
if err != nil {
|
||||
return fmt.Errorf("client key: %w", err)
|
||||
}
|
||||
|
||||
err = validateOpenVPNEncryptedKey(vpnProvider, *o.EncryptedKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("encrypted key: %w", err)
|
||||
}
|
||||
|
||||
if *o.EncryptedKey != "" && *o.KeyPassphrase == "" {
|
||||
return fmt.Errorf("%w", ErrOpenVPNKeyPassphraseIsEmpty)
|
||||
}
|
||||
|
||||
// Validate MSSFix
|
||||
const maxMSSFix = 10000
|
||||
if *o.MSSFix > maxMSSFix {
|
||||
return fmt.Errorf("%w: %d is over the maximum value of %d",
|
||||
@@ -152,7 +145,6 @@ func (o OpenVPN) validate(vpnProvider string) (err error) {
|
||||
ErrOpenVPNInterfaceNotValid, o.Interface, regexpInterfaceName)
|
||||
}
|
||||
|
||||
// Validate Verbosity
|
||||
if *o.Verbosity < 0 || *o.Verbosity > 6 {
|
||||
return fmt.Errorf("%w: %d can only be between 0 and 5",
|
||||
ErrOpenVPNVerbosityIsOutOfBounds, o.Verbosity)
|
||||
@@ -161,24 +153,112 @@ func (o OpenVPN) validate(vpnProvider string) (err error) {
|
||||
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("extracting information from custom configuration file: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateOpenVPNClientCertificate(vpnProvider,
|
||||
clientCert string) (err error) {
|
||||
switch vpnProvider {
|
||||
case
|
||||
providers.Airvpn,
|
||||
providers.Cyberghost,
|
||||
providers.VPNSecure,
|
||||
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.Airvpn,
|
||||
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 validateOpenVPNEncryptedKey(vpnProvider,
|
||||
encryptedPrivateKey string) (err error) {
|
||||
if vpnProvider == providers.VPNSecure && encryptedPrivateKey == "" {
|
||||
return ErrMissingValue
|
||||
}
|
||||
|
||||
if encryptedPrivateKey == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err = base64.StdEncoding.DecodeString(encryptedPrivateKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *OpenVPN) copy() (copied OpenVPN) {
|
||||
return OpenVPN{
|
||||
Version: o.Version,
|
||||
User: o.User,
|
||||
Password: o.Password,
|
||||
ConfFile: helpers.CopyStringPtr(o.ConfFile),
|
||||
Ciphers: helpers.CopyStringSlice(o.Ciphers),
|
||||
Auth: helpers.CopyStringPtr(o.Auth),
|
||||
ClientCrt: helpers.CopyStringPtr(o.ClientCrt),
|
||||
ClientKey: helpers.CopyStringPtr(o.ClientKey),
|
||||
PIAEncPreset: helpers.CopyStringPtr(o.PIAEncPreset),
|
||||
IPv6: helpers.CopyBoolPtr(o.IPv6),
|
||||
MSSFix: helpers.CopyUint16Ptr(o.MSSFix),
|
||||
Interface: o.Interface,
|
||||
Root: helpers.CopyBoolPtr(o.Root),
|
||||
ProcUser: o.ProcUser,
|
||||
Verbosity: helpers.CopyIntPtr(o.Verbosity),
|
||||
Flags: helpers.CopyStringSlice(o.Flags),
|
||||
Version: o.Version,
|
||||
User: helpers.CopyStringPtr(o.User),
|
||||
Password: helpers.CopyStringPtr(o.Password),
|
||||
ConfFile: helpers.CopyStringPtr(o.ConfFile),
|
||||
Ciphers: helpers.CopyStringSlice(o.Ciphers),
|
||||
Auth: helpers.CopyStringPtr(o.Auth),
|
||||
Cert: helpers.CopyStringPtr(o.Cert),
|
||||
Key: helpers.CopyStringPtr(o.Key),
|
||||
EncryptedKey: helpers.CopyStringPtr(o.EncryptedKey),
|
||||
KeyPassphrase: helpers.CopyStringPtr(o.KeyPassphrase),
|
||||
PIAEncPreset: helpers.CopyStringPtr(o.PIAEncPreset),
|
||||
MSSFix: helpers.CopyUint16Ptr(o.MSSFix),
|
||||
Interface: o.Interface,
|
||||
ProcessUser: o.ProcessUser,
|
||||
Verbosity: helpers.CopyIntPtr(o.Verbosity),
|
||||
Flags: helpers.CopyStringSlice(o.Flags),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,20 +266,20 @@ func (o *OpenVPN) copy() (copied OpenVPN) {
|
||||
// unset field of the receiver settings object.
|
||||
func (o *OpenVPN) mergeWith(other OpenVPN) {
|
||||
o.Version = helpers.MergeWithString(o.Version, other.Version)
|
||||
o.User = helpers.MergeWithString(o.User, other.User)
|
||||
o.Password = helpers.MergeWithString(o.Password, other.Password)
|
||||
o.User = helpers.MergeWithStringPtr(o.User, other.User)
|
||||
o.Password = helpers.MergeWithStringPtr(o.Password, other.Password)
|
||||
o.ConfFile = helpers.MergeWithStringPtr(o.ConfFile, other.ConfFile)
|
||||
o.Ciphers = helpers.MergeStringSlices(o.Ciphers, other.Ciphers)
|
||||
o.Auth = helpers.MergeWithStringPtr(o.Auth, other.Auth)
|
||||
o.ClientCrt = helpers.MergeWithStringPtr(o.ClientCrt, other.ClientCrt)
|
||||
o.ClientKey = helpers.MergeWithStringPtr(o.ClientKey, other.ClientKey)
|
||||
o.Cert = helpers.MergeWithStringPtr(o.Cert, other.Cert)
|
||||
o.Key = helpers.MergeWithStringPtr(o.Key, other.Key)
|
||||
o.EncryptedKey = helpers.MergeWithStringPtr(o.EncryptedKey, other.EncryptedKey)
|
||||
o.KeyPassphrase = helpers.MergeWithStringPtr(o.KeyPassphrase, other.KeyPassphrase)
|
||||
o.PIAEncPreset = helpers.MergeWithStringPtr(o.PIAEncPreset, other.PIAEncPreset)
|
||||
o.IPv6 = helpers.MergeWithBool(o.IPv6, other.IPv6)
|
||||
o.MSSFix = helpers.MergeWithUint16(o.MSSFix, other.MSSFix)
|
||||
o.Interface = helpers.MergeWithString(o.Interface, other.Interface)
|
||||
o.Root = helpers.MergeWithBool(o.Root, other.Root)
|
||||
o.ProcUser = helpers.MergeWithString(o.ProcUser, other.ProcUser)
|
||||
o.Verbosity = helpers.MergeWithInt(o.Verbosity, other.Verbosity)
|
||||
o.ProcessUser = helpers.MergeWithString(o.ProcessUser, other.ProcessUser)
|
||||
o.Verbosity = helpers.MergeWithIntPtr(o.Verbosity, other.Verbosity)
|
||||
o.Flags = helpers.MergeStringSlices(o.Flags, other.Flags)
|
||||
}
|
||||
|
||||
@@ -208,45 +288,47 @@ func (o *OpenVPN) mergeWith(other OpenVPN) {
|
||||
// settings.
|
||||
func (o *OpenVPN) overrideWith(other OpenVPN) {
|
||||
o.Version = helpers.OverrideWithString(o.Version, other.Version)
|
||||
o.User = helpers.OverrideWithString(o.User, other.User)
|
||||
o.Password = helpers.OverrideWithString(o.Password, other.Password)
|
||||
o.User = helpers.OverrideWithStringPtr(o.User, other.User)
|
||||
o.Password = helpers.OverrideWithStringPtr(o.Password, other.Password)
|
||||
o.ConfFile = helpers.OverrideWithStringPtr(o.ConfFile, other.ConfFile)
|
||||
o.Ciphers = helpers.OverrideWithStringSlice(o.Ciphers, other.Ciphers)
|
||||
o.Auth = helpers.OverrideWithStringPtr(o.Auth, other.Auth)
|
||||
o.ClientCrt = helpers.OverrideWithStringPtr(o.ClientCrt, other.ClientCrt)
|
||||
o.ClientKey = helpers.OverrideWithStringPtr(o.ClientKey, other.ClientKey)
|
||||
o.Cert = helpers.OverrideWithStringPtr(o.Cert, other.Cert)
|
||||
o.Key = helpers.OverrideWithStringPtr(o.Key, other.Key)
|
||||
o.EncryptedKey = helpers.OverrideWithStringPtr(o.EncryptedKey, other.EncryptedKey)
|
||||
o.KeyPassphrase = helpers.OverrideWithStringPtr(o.KeyPassphrase, other.KeyPassphrase)
|
||||
o.PIAEncPreset = helpers.OverrideWithStringPtr(o.PIAEncPreset, other.PIAEncPreset)
|
||||
o.IPv6 = helpers.OverrideWithBool(o.IPv6, other.IPv6)
|
||||
o.MSSFix = helpers.OverrideWithUint16(o.MSSFix, other.MSSFix)
|
||||
o.Interface = helpers.OverrideWithString(o.Interface, other.Interface)
|
||||
o.Root = helpers.OverrideWithBool(o.Root, other.Root)
|
||||
o.ProcUser = helpers.OverrideWithString(o.ProcUser, other.ProcUser)
|
||||
o.Verbosity = helpers.OverrideWithInt(o.Verbosity, other.Verbosity)
|
||||
o.ProcessUser = helpers.OverrideWithString(o.ProcessUser, other.ProcessUser)
|
||||
o.Verbosity = helpers.OverrideWithIntPtr(o.Verbosity, other.Verbosity)
|
||||
o.Flags = helpers.OverrideWithStringSlice(o.Flags, other.Flags)
|
||||
}
|
||||
|
||||
func (o *OpenVPN) setDefaults(vpnProvider string) {
|
||||
o.Version = helpers.DefaultString(o.Version, constants.Openvpn25)
|
||||
if vpnProvider == constants.Mullvad {
|
||||
o.Password = "m"
|
||||
o.Version = helpers.DefaultString(o.Version, openvpn.Openvpn25)
|
||||
o.User = helpers.DefaultStringPtr(o.User, "")
|
||||
if vpnProvider == providers.Mullvad {
|
||||
o.Password = helpers.DefaultStringPtr(o.Password, "m")
|
||||
} else {
|
||||
o.Password = helpers.DefaultStringPtr(o.Password, "")
|
||||
}
|
||||
|
||||
o.ConfFile = helpers.DefaultStringPtr(o.ConfFile, "")
|
||||
o.Auth = helpers.DefaultStringPtr(o.Auth, "")
|
||||
o.ClientCrt = helpers.DefaultStringPtr(o.ClientCrt, "")
|
||||
o.ClientKey = helpers.DefaultStringPtr(o.ClientKey, "")
|
||||
o.Cert = helpers.DefaultStringPtr(o.Cert, "")
|
||||
o.Key = helpers.DefaultStringPtr(o.Key, "")
|
||||
o.EncryptedKey = helpers.DefaultStringPtr(o.EncryptedKey, "")
|
||||
o.KeyPassphrase = helpers.DefaultStringPtr(o.KeyPassphrase, "")
|
||||
|
||||
var defaultEncPreset string
|
||||
if vpnProvider == constants.PrivateInternetAccess {
|
||||
defaultEncPreset = constants.PIAEncryptionPresetStrong
|
||||
if vpnProvider == providers.PrivateInternetAccess {
|
||||
defaultEncPreset = presets.Strong
|
||||
}
|
||||
o.PIAEncPreset = helpers.DefaultStringPtr(o.PIAEncPreset, defaultEncPreset)
|
||||
|
||||
o.IPv6 = helpers.DefaultBool(o.IPv6, false)
|
||||
o.MSSFix = helpers.DefaultUint16(o.MSSFix, 0)
|
||||
o.Interface = helpers.DefaultString(o.Interface, "tun0")
|
||||
o.Root = helpers.DefaultBool(o.Root, true)
|
||||
o.ProcUser = helpers.DefaultString(o.ProcUser, "root")
|
||||
o.ProcessUser = helpers.DefaultString(o.ProcessUser, "root")
|
||||
o.Verbosity = helpers.DefaultInt(o.Verbosity, 1)
|
||||
}
|
||||
|
||||
@@ -257,8 +339,8 @@ func (o OpenVPN) String() string {
|
||||
func (o OpenVPN) toLinesNode() (node *gotree.Node) {
|
||||
node = gotree.New("OpenVPN settings:")
|
||||
node.Appendf("OpenVPN version: %s", o.Version)
|
||||
node.Appendf("User: %s", helpers.ObfuscatePassword(o.User))
|
||||
node.Appendf("Password: %s", helpers.ObfuscatePassword(o.Password))
|
||||
node.Appendf("User: %s", helpers.ObfuscatePassword(*o.User))
|
||||
node.Appendf("Password: %s", helpers.ObfuscatePassword(*o.Password))
|
||||
|
||||
if *o.ConfFile != "" {
|
||||
node.Appendf("Custom configuration file: %s", *o.ConfFile)
|
||||
@@ -272,20 +354,23 @@ func (o OpenVPN) toLinesNode() (node *gotree.Node) {
|
||||
node.Appendf("Auth: %s", *o.Auth)
|
||||
}
|
||||
|
||||
if *o.ClientCrt != "" {
|
||||
node.Appendf("Client crt: %s", helpers.ObfuscateData(*o.ClientCrt))
|
||||
if *o.Cert != "" {
|
||||
node.Appendf("Client crt: %s", helpers.ObfuscateData(*o.Cert))
|
||||
}
|
||||
|
||||
if *o.ClientKey != "" {
|
||||
node.Appendf("Client key: %s", helpers.ObfuscateData(*o.ClientKey))
|
||||
if *o.Key != "" {
|
||||
node.Appendf("Client key: %s", helpers.ObfuscateData(*o.Key))
|
||||
}
|
||||
|
||||
if *o.EncryptedKey != "" {
|
||||
node.Appendf("Encrypted key: %s (key passhrapse %s)",
|
||||
helpers.ObfuscateData(*o.EncryptedKey), helpers.ObfuscatePassword(*o.KeyPassphrase))
|
||||
}
|
||||
|
||||
if *o.PIAEncPreset != "" {
|
||||
node.Appendf("Private Internet Access encryption preset: %s", *o.PIAEncPreset)
|
||||
}
|
||||
|
||||
node.Appendf("Tunnel IPv6: %s", helpers.BoolPtrToYesNo(o.IPv6))
|
||||
|
||||
if *o.MSSFix > 0 {
|
||||
node.Appendf("MSS Fix: %d", *o.MSSFix)
|
||||
}
|
||||
@@ -294,14 +379,7 @@ func (o OpenVPN) toLinesNode() (node *gotree.Node) {
|
||||
node.Appendf("Network interface: %s", o.Interface)
|
||||
}
|
||||
|
||||
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("Run OpenVPN as: %s", o.ProcessUser)
|
||||
|
||||
node.Appendf("Verbosity level: %d", *o.Verbosity)
|
||||
|
||||
|
||||
44
internal/configuration/settings/openvpn_test.go
Normal file
44
internal/configuration/settings/openvpn_test.go
Normal file
@@ -0,0 +1,44 @@
|
||||
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,7 +4,8 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||
"github.com/qdm12/gluetun/internal/provider/privateinternetaccess/presets"
|
||||
"github.com/qdm12/gotree"
|
||||
)
|
||||
|
||||
@@ -33,15 +34,17 @@ func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
|
||||
if confFile := *o.ConfFile; confFile != "" {
|
||||
err := helpers.FileExists(confFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %s", ErrOpenVPNConfigFile, err)
|
||||
return fmt.Errorf("configuration file: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Validate TCP
|
||||
if *o.TCP && helpers.IsOneOf(vpnProvider,
|
||||
constants.Perfectprivacy,
|
||||
constants.Privado,
|
||||
constants.Vyprvpn,
|
||||
providers.Ipvanish,
|
||||
providers.Perfectprivacy,
|
||||
providers.Privado,
|
||||
providers.VPNUnlimited,
|
||||
providers.Vyprvpn,
|
||||
) {
|
||||
return fmt.Errorf("%w: for VPN service provider %s",
|
||||
ErrOpenVPNTCPNotSupported, vpnProvider)
|
||||
@@ -51,33 +54,47 @@ func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
|
||||
if *o.CustomPort != 0 {
|
||||
switch vpnProvider {
|
||||
// no restriction on port
|
||||
case constants.Cyberghost, constants.HideMyAss,
|
||||
constants.PrivateInternetAccess, constants.Privatevpn,
|
||||
constants.Protonvpn, constants.Torguard:
|
||||
case providers.Cyberghost, providers.HideMyAss,
|
||||
providers.Privatevpn, providers.Torguard:
|
||||
// no custom port allowed
|
||||
case constants.Expressvpn, constants.Fastestvpn,
|
||||
constants.Ipvanish, constants.Nordvpn,
|
||||
constants.Privado, constants.Purevpn,
|
||||
constants.Surfshark, constants.VPNUnlimited,
|
||||
constants.Vyprvpn:
|
||||
case providers.Expressvpn, providers.Fastestvpn,
|
||||
providers.Ipvanish, providers.Nordvpn,
|
||||
providers.Privado, providers.Purevpn,
|
||||
providers.Surfshark, providers.VPNSecure,
|
||||
providers.VPNUnlimited, providers.Vyprvpn:
|
||||
return fmt.Errorf("%w: for VPN service provider %s",
|
||||
ErrOpenVPNCustomPortNotAllowed, vpnProvider)
|
||||
default:
|
||||
var allowedTCP, allowedUDP []uint16
|
||||
switch vpnProvider {
|
||||
case constants.Ivpn:
|
||||
case providers.Airvpn:
|
||||
allowedTCP = []uint16{
|
||||
53, 80, 443, // IP in 1, 3
|
||||
1194, 2018, 41185, // IP in 1, 2, 3, 4
|
||||
}
|
||||
allowedUDP = []uint16{53, 80, 443, 1194, 2018, 41185}
|
||||
case providers.Ivpn:
|
||||
allowedTCP = []uint16{80, 443, 1143}
|
||||
allowedUDP = []uint16{53, 1194, 2049, 2050}
|
||||
case constants.Mullvad:
|
||||
case providers.Mullvad:
|
||||
allowedTCP = []uint16{80, 443, 1401}
|
||||
allowedUDP = []uint16{53, 1194, 1195, 1196, 1197, 1300, 1301, 1302, 1303, 1400}
|
||||
case constants.Perfectprivacy:
|
||||
case providers.Perfectprivacy:
|
||||
allowedTCP = []uint16{44, 443, 4433}
|
||||
allowedUDP = []uint16{44, 443, 4433}
|
||||
case constants.Wevpn:
|
||||
case providers.PrivateInternetAccess:
|
||||
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.SlickVPN:
|
||||
allowedTCP = []uint16{443, 8080, 8888}
|
||||
allowedUDP = []uint16{443, 8080, 8888}
|
||||
case providers.Wevpn:
|
||||
allowedTCP = []uint16{53, 1195, 1199, 2018}
|
||||
allowedUDP = []uint16{80, 1194, 1198}
|
||||
case constants.Windscribe:
|
||||
case providers.Windscribe:
|
||||
allowedTCP = []uint16{21, 22, 80, 123, 143, 443, 587, 1194, 3306, 8080, 54783}
|
||||
allowedUDP = []uint16{53, 80, 123, 443, 1194, 54783}
|
||||
}
|
||||
@@ -95,11 +112,11 @@ func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
|
||||
}
|
||||
|
||||
// Validate EncPreset
|
||||
if vpnProvider == constants.PrivateInternetAccess {
|
||||
if vpnProvider == providers.PrivateInternetAccess {
|
||||
validEncryptionPresets := []string{
|
||||
constants.PIAEncryptionPresetNone,
|
||||
constants.PIAEncryptionPresetNormal,
|
||||
constants.PIAEncryptionPresetStrong,
|
||||
presets.None,
|
||||
presets.Normal,
|
||||
presets.Strong,
|
||||
}
|
||||
if !helpers.IsOneOf(*o.PIAEncPreset, validEncryptionPresets...) {
|
||||
return fmt.Errorf("%w: %s; valid presets are %s",
|
||||
@@ -140,8 +157,8 @@ func (o *OpenVPNSelection) setDefaults(vpnProvider string) {
|
||||
o.CustomPort = helpers.DefaultUint16(o.CustomPort, 0)
|
||||
|
||||
var defaultEncPreset string
|
||||
if vpnProvider == constants.PrivateInternetAccess {
|
||||
defaultEncPreset = constants.PIAEncryptionPresetStrong
|
||||
if vpnProvider == providers.PrivateInternetAccess {
|
||||
defaultEncPreset = presets.Strong
|
||||
}
|
||||
o.PIAEncPreset = helpers.DefaultStringPtr(o.PIAEncPreset, defaultEncPreset)
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||
"github.com/qdm12/gotree"
|
||||
)
|
||||
|
||||
@@ -28,7 +28,7 @@ func (p PortForwarding) validate(vpnProvider string) (err error) {
|
||||
}
|
||||
|
||||
// Validate Enabled
|
||||
validProviders := []string{constants.PrivateInternetAccess}
|
||||
validProviders := []string{providers.PrivateInternetAccess}
|
||||
if !helpers.IsOneOf(vpnProvider, validProviders...) {
|
||||
return fmt.Errorf("%w: for provider %s, it is only available for %s",
|
||||
ErrPortForwardingEnabled, vpnProvider, strings.Join(validProviders, ", "))
|
||||
@@ -38,7 +38,7 @@ func (p PortForwarding) validate(vpnProvider string) (err error) {
|
||||
if *p.Filepath != "" { // optional
|
||||
_, err := filepath.Abs(*p.Filepath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %s", ErrPortForwardingFilepathNotValid, err)
|
||||
return fmt.Errorf("filepath is not valid: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||
"github.com/qdm12/gluetun/internal/constants/vpn"
|
||||
"github.com/qdm12/gotree"
|
||||
)
|
||||
|
||||
@@ -22,18 +22,20 @@ type Provider struct {
|
||||
}
|
||||
|
||||
// TODO v4 remove pointer for receiver (because of Surfshark).
|
||||
func (p *Provider) validate(vpnType string, allServers models.AllServers) (err error) {
|
||||
func (p *Provider) validate(vpnType string, storage Storage) (err error) {
|
||||
// Validate Name
|
||||
var validNames []string
|
||||
if vpnType == constants.OpenVPN {
|
||||
validNames = constants.AllProviders()
|
||||
if vpnType == vpn.OpenVPN {
|
||||
validNames = providers.AllWithCustom()
|
||||
validNames = append(validNames, "pia") // Retro-compatibility
|
||||
} else { // Wireguard
|
||||
validNames = []string{
|
||||
constants.Custom,
|
||||
constants.Ivpn,
|
||||
constants.Mullvad,
|
||||
constants.Windscribe,
|
||||
providers.Airvpn,
|
||||
providers.Custom,
|
||||
providers.Ivpn,
|
||||
providers.Mullvad,
|
||||
providers.Surfshark,
|
||||
providers.Windscribe,
|
||||
}
|
||||
}
|
||||
if !helpers.IsOneOf(*p.Name, validNames...) {
|
||||
@@ -41,14 +43,14 @@ func (p *Provider) validate(vpnType string, allServers models.AllServers) (err e
|
||||
ErrVPNProviderNameNotValid, *p.Name, helpers.ChoicesOrString(validNames))
|
||||
}
|
||||
|
||||
err = p.ServerSelection.validate(*p.Name, allServers)
|
||||
err = p.ServerSelection.validate(*p.Name, storage)
|
||||
if err != nil {
|
||||
return fmt.Errorf("server selection settings validation failed: %w", err)
|
||||
return fmt.Errorf("server selection: %w", err)
|
||||
}
|
||||
|
||||
err = p.PortForwarding.validate(*p.Name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("port forwarding settings validation failed: %w", err)
|
||||
return fmt.Errorf("port forwarding: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -75,7 +77,7 @@ func (p *Provider) overrideWith(other Provider) {
|
||||
}
|
||||
|
||||
func (p *Provider) setDefaults() {
|
||||
p.Name = helpers.DefaultStringPtr(p.Name, constants.PrivateInternetAccess)
|
||||
p.Name = helpers.DefaultStringPtr(p.Name, providers.PrivateInternetAccess)
|
||||
p.ServerSelection.setDefaults(*p.Name)
|
||||
p.PortForwarding.setDefaults()
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ func (p PublicIP) validate() (err error) {
|
||||
if *p.IPFilepath != "" { // optional
|
||||
_, err := filepath.Abs(*p.IPFilepath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %s", ErrPublicIPFilepathNotValid, err)
|
||||
return fmt.Errorf("filepath is not valid: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,18 +48,18 @@ func (p *PublicIP) copy() (copied PublicIP) {
|
||||
}
|
||||
|
||||
func (p *PublicIP) mergeWith(other PublicIP) {
|
||||
p.Period = helpers.MergeWithDuration(p.Period, other.Period)
|
||||
p.Period = helpers.MergeWithDurationPtr(p.Period, other.Period)
|
||||
p.IPFilepath = helpers.MergeWithStringPtr(p.IPFilepath, other.IPFilepath)
|
||||
}
|
||||
|
||||
func (p *PublicIP) overrideWith(other PublicIP) {
|
||||
p.Period = helpers.OverrideWithDuration(p.Period, other.Period)
|
||||
p.Period = helpers.OverrideWithDurationPtr(p.Period, other.Period)
|
||||
p.IPFilepath = helpers.OverrideWithStringPtr(p.IPFilepath, other.IPFilepath)
|
||||
}
|
||||
|
||||
func (p *PublicIP) setDefaults() {
|
||||
const defaultPeriod = 12 * time.Hour
|
||||
p.Period = helpers.DefaultDuration(p.Period, defaultPeriod)
|
||||
p.Period = helpers.DefaultDurationPtr(p.Period, defaultPeriod)
|
||||
p.IPFilepath = helpers.DefaultStringPtr(p.IPFilepath, "/tmp/gluetun/ip")
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,9 @@ package settings
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||
"github.com/qdm12/gotree"
|
||||
@@ -10,22 +12,30 @@ import (
|
||||
|
||||
// ControlServer contains settings to customize the control server operation.
|
||||
type ControlServer struct {
|
||||
// Port is the listening port to use.
|
||||
// It can be set to 0 to bind to a random port.
|
||||
// Address is the listening address to use.
|
||||
// It cannot be nil in the internal state.
|
||||
// TODO change to address
|
||||
Port *uint16
|
||||
Address *string
|
||||
// Log can be true or false to enable logging on requests.
|
||||
// It cannot be nil in the internal state.
|
||||
Log *bool
|
||||
}
|
||||
|
||||
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()
|
||||
const maxPrivilegedPort uint16 = 1023
|
||||
if uid != 0 && *c.Port <= maxPrivilegedPort {
|
||||
const maxPrivilegedPort = 1023
|
||||
if uid != 0 && port != 0 && port <= maxPrivilegedPort {
|
||||
return fmt.Errorf("%w: %d when running with user ID %d",
|
||||
ErrControlServerPrivilegedPort, *c.Port, uid)
|
||||
ErrControlServerPrivilegedPort, port, uid)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -33,15 +43,15 @@ func (c ControlServer) validate() (err error) {
|
||||
|
||||
func (c *ControlServer) copy() (copied ControlServer) {
|
||||
return ControlServer{
|
||||
Port: helpers.CopyUint16Ptr(c.Port),
|
||||
Log: helpers.CopyBoolPtr(c.Log),
|
||||
Address: helpers.CopyStringPtr(c.Address),
|
||||
Log: helpers.CopyBoolPtr(c.Log),
|
||||
}
|
||||
}
|
||||
|
||||
// mergeWith merges the other settings into any
|
||||
// unset field of the receiver settings object.
|
||||
func (c *ControlServer) mergeWith(other ControlServer) {
|
||||
c.Port = helpers.MergeWithUint16(c.Port, other.Port)
|
||||
c.Address = helpers.MergeWithStringPtr(c.Address, other.Address)
|
||||
c.Log = helpers.MergeWithBool(c.Log, other.Log)
|
||||
}
|
||||
|
||||
@@ -49,13 +59,12 @@ func (c *ControlServer) mergeWith(other ControlServer) {
|
||||
// settings object with any field set in the other
|
||||
// settings.
|
||||
func (c *ControlServer) overrideWith(other ControlServer) {
|
||||
c.Port = helpers.MergeWithUint16(c.Port, other.Port)
|
||||
c.Log = helpers.MergeWithBool(c.Log, other.Log)
|
||||
c.Address = helpers.OverrideWithStringPtr(c.Address, other.Address)
|
||||
c.Log = helpers.OverrideWithBool(c.Log, other.Log)
|
||||
}
|
||||
|
||||
func (c *ControlServer) setDefaults() {
|
||||
const defaultPort = 8000
|
||||
c.Port = helpers.DefaultUint16(c.Port, defaultPort)
|
||||
c.Address = helpers.DefaultStringPtr(c.Address, ":8000")
|
||||
c.Log = helpers.DefaultBool(c.Log, true)
|
||||
}
|
||||
|
||||
@@ -65,7 +74,7 @@ func (c ControlServer) String() string {
|
||||
|
||||
func (c ControlServer) toLinesNode() (node *gotree.Node) {
|
||||
node = gotree.New("Control server settings:")
|
||||
node.Appendf("Listening port: %d", *c.Port)
|
||||
node.Appendf("Listening address: %s", *c.Address)
|
||||
node.Appendf("Logging: %s", helpers.BoolPtrToYesNo(c.Log))
|
||||
return node
|
||||
}
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
package settings
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings/validation"
|
||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||
"github.com/qdm12/gluetun/internal/constants/vpn"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/qdm12/gotree"
|
||||
)
|
||||
@@ -36,16 +39,20 @@ type ServerSelection struct { //nolint:maligned
|
||||
Numbers []uint16
|
||||
// Hostnames is the list of hostnames to filter VPN servers with.
|
||||
Hostnames []string
|
||||
// OwnedOnly is true if only VPN provider owned servers
|
||||
// OwnedOnly is true if VPN provider servers that are not owned
|
||||
// should be filtered. This is used with Mullvad.
|
||||
OwnedOnly *bool
|
||||
// FreeOnly is true if only free VPN servers
|
||||
// should be filtered. This is used with ProtonVPN.
|
||||
// FreeOnly is true if VPN servers that are not free should
|
||||
// be filtered. This is used with ProtonVPN and VPN Unlimited.
|
||||
FreeOnly *bool
|
||||
// FreeOnly is true if only free VPN servers
|
||||
// should be filtered. This is used with ProtonVPN.
|
||||
// PremiumOnly is true if VPN servers that are not premium should
|
||||
// be filtered. This is used with VPN Secure.
|
||||
// TODO extend to providers using FreeOnly.
|
||||
PremiumOnly *bool
|
||||
// StreamOnly is true if VPN servers not for streaming should
|
||||
// be filtered. This is used with VPNUnlimited.
|
||||
StreamOnly *bool
|
||||
// MultiHopOnly is true if only multihop VPN servers
|
||||
// MultiHopOnly is true if VPN servers that are not multihop
|
||||
// should be filtered. This is used with Surfshark.
|
||||
MultiHopOnly *bool
|
||||
|
||||
@@ -57,190 +64,136 @@ type ServerSelection struct { //nolint:maligned
|
||||
Wireguard WireguardSelection
|
||||
}
|
||||
|
||||
var (
|
||||
ErrOwnedOnlyNotSupported = errors.New("owned only filter is not supported")
|
||||
ErrFreeOnlyNotSupported = errors.New("free only filter is not supported")
|
||||
ErrPremiumOnlyNotSupported = errors.New("premium only filter is not supported")
|
||||
ErrStreamOnlyNotSupported = errors.New("stream only filter is not supported")
|
||||
ErrMultiHopOnlyNotSupported = errors.New("multi hop only filter is not supported")
|
||||
ErrFreePremiumBothSet = errors.New("free only and premium only filters are both set")
|
||||
)
|
||||
|
||||
func (ss *ServerSelection) validate(vpnServiceProvider string,
|
||||
allServers models.AllServers) (err error) {
|
||||
storage Storage) (err error) {
|
||||
switch ss.VPN {
|
||||
case constants.OpenVPN, constants.Wireguard:
|
||||
case vpn.OpenVPN, vpn.Wireguard:
|
||||
default:
|
||||
return fmt.Errorf("%w: %s", ErrVPNTypeNotValid, ss.VPN)
|
||||
}
|
||||
|
||||
var countryChoices, regionChoices, cityChoices,
|
||||
ispChoices, nameChoices, hostnameChoices []string
|
||||
switch vpnServiceProvider {
|
||||
case constants.Custom:
|
||||
case constants.Cyberghost:
|
||||
servers := allServers.GetCyberghost()
|
||||
countryChoices = constants.CyberghostCountryChoices(servers)
|
||||
hostnameChoices = constants.CyberghostHostnameChoices(servers)
|
||||
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)
|
||||
filterChoices, err := getLocationFilterChoices(vpnServiceProvider, ss, storage)
|
||||
if err != nil {
|
||||
return err // already wrapped error
|
||||
}
|
||||
|
||||
if ss.VPN == constants.OpenVPN {
|
||||
err = validateServerFilters(*ss, filterChoices)
|
||||
if err != nil {
|
||||
if errors.Is(err, helpers.ErrNoChoice) {
|
||||
return fmt.Errorf("for VPN service provider %s: %w", vpnServiceProvider, err)
|
||||
}
|
||||
return err // already wrapped error
|
||||
}
|
||||
|
||||
if *ss.OwnedOnly &&
|
||||
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.PremiumOnly &&
|
||||
!helpers.IsOneOf(vpnServiceProvider,
|
||||
providers.VPNSecure,
|
||||
) {
|
||||
return fmt.Errorf("%w: for VPN service provider %s",
|
||||
ErrPremiumOnlyNotSupported, vpnServiceProvider)
|
||||
}
|
||||
|
||||
if *ss.FreeOnly && *ss.PremiumOnly {
|
||||
return ErrFreePremiumBothSet
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
return fmt.Errorf("OpenVPN server selection settings validation failed: %w", err)
|
||||
return fmt.Errorf("OpenVPN server selection settings: %w", err)
|
||||
}
|
||||
} else {
|
||||
err = ss.Wireguard.validate(vpnServiceProvider)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Wireguard server selection settings validation failed: %w", err)
|
||||
return fmt.Errorf("Wireguard server selection settings: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
// Set an argument to nil to pass the check for a particular filter.
|
||||
func validateServerFilters(settings ServerSelection,
|
||||
countryChoices, regionChoices, cityChoices, ispChoices,
|
||||
nameChoices, hostnameChoices []string) (err error) {
|
||||
if countryChoices != nil {
|
||||
if err := helpers.AreAllOneOf(settings.Countries, countryChoices); err != nil {
|
||||
return fmt.Errorf("%w: %s", ErrCountryNotValid, err)
|
||||
}
|
||||
func validateServerFilters(settings ServerSelection, filterChoices models.FilterChoices) (err error) {
|
||||
if err := helpers.AreAllOneOf(settings.Countries, filterChoices.Countries); err != nil {
|
||||
return fmt.Errorf("%w: %s", ErrCountryNotValid, err)
|
||||
}
|
||||
|
||||
if regionChoices != nil {
|
||||
if err := helpers.AreAllOneOf(settings.Regions, regionChoices); err != nil {
|
||||
return fmt.Errorf("%w: %s", ErrRegionNotValid, err)
|
||||
}
|
||||
if err := helpers.AreAllOneOf(settings.Regions, filterChoices.Regions); err != nil {
|
||||
return fmt.Errorf("%w: %s", ErrRegionNotValid, err)
|
||||
}
|
||||
|
||||
if cityChoices != nil {
|
||||
if err := helpers.AreAllOneOf(settings.Cities, cityChoices); err != nil {
|
||||
return fmt.Errorf("%w: %s", ErrCityNotValid, err)
|
||||
}
|
||||
if err := helpers.AreAllOneOf(settings.Cities, filterChoices.Cities); err != nil {
|
||||
return fmt.Errorf("%w: %s", ErrCityNotValid, err)
|
||||
}
|
||||
|
||||
if ispChoices != nil {
|
||||
if err := helpers.AreAllOneOf(settings.ISPs, ispChoices); err != nil {
|
||||
return fmt.Errorf("%w: %s", ErrISPNotValid, err)
|
||||
}
|
||||
if err := helpers.AreAllOneOf(settings.ISPs, filterChoices.ISPs); err != nil {
|
||||
return fmt.Errorf("%w: %s", ErrISPNotValid, err)
|
||||
}
|
||||
|
||||
if hostnameChoices != nil {
|
||||
if err := helpers.AreAllOneOf(settings.Hostnames, hostnameChoices); err != nil {
|
||||
return fmt.Errorf("%w: %s", ErrHostnameNotValid, err)
|
||||
}
|
||||
if err := helpers.AreAllOneOf(settings.Hostnames, filterChoices.Hostnames); err != nil {
|
||||
return fmt.Errorf("%w: %s", ErrHostnameNotValid, err)
|
||||
}
|
||||
|
||||
if nameChoices != nil {
|
||||
if err := helpers.AreAllOneOf(settings.Names, nameChoices); err != nil {
|
||||
return fmt.Errorf("%w: %s", ErrNameNotValid, err)
|
||||
}
|
||||
if err := helpers.AreAllOneOf(settings.Names, filterChoices.Names); err != nil {
|
||||
return fmt.Errorf("%w: %s", ErrNameNotValid, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -259,6 +212,7 @@ func (ss *ServerSelection) copy() (copied ServerSelection) {
|
||||
Numbers: helpers.CopyUint16Slice(ss.Numbers),
|
||||
OwnedOnly: helpers.CopyBoolPtr(ss.OwnedOnly),
|
||||
FreeOnly: helpers.CopyBoolPtr(ss.FreeOnly),
|
||||
PremiumOnly: helpers.CopyBoolPtr(ss.PremiumOnly),
|
||||
StreamOnly: helpers.CopyBoolPtr(ss.StreamOnly),
|
||||
MultiHopOnly: helpers.CopyBoolPtr(ss.MultiHopOnly),
|
||||
OpenVPN: ss.OpenVPN.copy(),
|
||||
@@ -278,6 +232,7 @@ func (ss *ServerSelection) mergeWith(other ServerSelection) {
|
||||
ss.Numbers = helpers.MergeUint16Slices(ss.Numbers, other.Numbers)
|
||||
ss.OwnedOnly = helpers.MergeWithBool(ss.OwnedOnly, other.OwnedOnly)
|
||||
ss.FreeOnly = helpers.MergeWithBool(ss.FreeOnly, other.FreeOnly)
|
||||
ss.PremiumOnly = helpers.MergeWithBool(ss.PremiumOnly, other.PremiumOnly)
|
||||
ss.StreamOnly = helpers.MergeWithBool(ss.StreamOnly, other.StreamOnly)
|
||||
ss.MultiHopOnly = helpers.MergeWithBool(ss.MultiHopOnly, other.MultiHopOnly)
|
||||
|
||||
@@ -297,6 +252,7 @@ func (ss *ServerSelection) overrideWith(other ServerSelection) {
|
||||
ss.Numbers = helpers.OverrideWithUint16Slice(ss.Numbers, other.Numbers)
|
||||
ss.OwnedOnly = helpers.OverrideWithBool(ss.OwnedOnly, other.OwnedOnly)
|
||||
ss.FreeOnly = helpers.OverrideWithBool(ss.FreeOnly, other.FreeOnly)
|
||||
ss.PremiumOnly = helpers.OverrideWithBool(ss.PremiumOnly, other.PremiumOnly)
|
||||
ss.StreamOnly = helpers.OverrideWithBool(ss.StreamOnly, other.StreamOnly)
|
||||
ss.MultiHopOnly = helpers.OverrideWithBool(ss.MultiHopOnly, other.MultiHopOnly)
|
||||
ss.OpenVPN.overrideWith(other.OpenVPN)
|
||||
@@ -304,10 +260,11 @@ func (ss *ServerSelection) overrideWith(other ServerSelection) {
|
||||
}
|
||||
|
||||
func (ss *ServerSelection) setDefaults(vpnProvider string) {
|
||||
ss.VPN = helpers.DefaultString(ss.VPN, constants.OpenVPN)
|
||||
ss.VPN = helpers.DefaultString(ss.VPN, vpn.OpenVPN)
|
||||
ss.TargetIP = helpers.DefaultIP(ss.TargetIP, net.IP{})
|
||||
ss.OwnedOnly = helpers.DefaultBool(ss.OwnedOnly, false)
|
||||
ss.FreeOnly = helpers.DefaultBool(ss.FreeOnly, false)
|
||||
ss.PremiumOnly = helpers.DefaultBool(ss.PremiumOnly, false)
|
||||
ss.StreamOnly = helpers.DefaultBool(ss.StreamOnly, false)
|
||||
ss.MultiHopOnly = helpers.DefaultBool(ss.MultiHopOnly, false)
|
||||
ss.OpenVPN.setDefaults(vpnProvider)
|
||||
@@ -364,6 +321,10 @@ func (ss ServerSelection) toLinesNode() (node *gotree.Node) {
|
||||
node.Appendf("Free only servers: yes")
|
||||
}
|
||||
|
||||
if *ss.PremiumOnly {
|
||||
node.Appendf("Premium only servers: yes")
|
||||
}
|
||||
|
||||
if *ss.StreamOnly {
|
||||
node.Appendf("Stream only servers: yes")
|
||||
}
|
||||
@@ -372,7 +333,7 @@ func (ss ServerSelection) toLinesNode() (node *gotree.Node) {
|
||||
node.Appendf("Multi-hop only servers: yes")
|
||||
}
|
||||
|
||||
if ss.VPN == constants.OpenVPN {
|
||||
if ss.VPN == vpn.OpenVPN {
|
||||
node.AppendNode(ss.OpenVPN.toLinesNode())
|
||||
} else {
|
||||
node.AppendNode(ss.Wireguard.toLinesNode())
|
||||
|
||||
@@ -3,7 +3,12 @@ package settings
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||
"github.com/qdm12/gluetun/internal/constants/openvpn"
|
||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||
"github.com/qdm12/gluetun/internal/constants/vpn"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/qdm12/gluetun/internal/pprof"
|
||||
"github.com/qdm12/gotree"
|
||||
)
|
||||
|
||||
@@ -20,12 +25,17 @@ type Settings struct {
|
||||
Updater Updater
|
||||
Version Version
|
||||
VPN VPN
|
||||
Pprof pprof.Settings
|
||||
}
|
||||
|
||||
type Storage interface {
|
||||
GetFilterChoices(provider string) models.FilterChoices
|
||||
}
|
||||
|
||||
// Validate validates all the settings and returns an error
|
||||
// if one of them is not valid.
|
||||
// TODO v4 remove pointer for receiver (because of Surfshark).
|
||||
func (s *Settings) Validate(allServers models.AllServers) (err error) {
|
||||
func (s *Settings) Validate(storage Storage, ipv6Supported bool) (err error) {
|
||||
nameToValidation := map[string]func() error{
|
||||
"control server": s.ControlServer.validate,
|
||||
"dns": s.DNS.validate,
|
||||
@@ -38,15 +48,16 @@ func (s *Settings) Validate(allServers models.AllServers) (err error) {
|
||||
"system": s.System.validate,
|
||||
"updater": s.Updater.Validate,
|
||||
"version": s.Version.validate,
|
||||
// Pprof validation done in pprof constructor
|
||||
"VPN": func() error {
|
||||
return s.VPN.validate(allServers)
|
||||
return s.VPN.Validate(storage, ipv6Supported)
|
||||
},
|
||||
}
|
||||
|
||||
for name, validation := range nameToValidation {
|
||||
err = validation()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed validating %s settings: %w", name, err)
|
||||
return fmt.Errorf("%s settings: %w", name, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,7 +77,8 @@ func (s *Settings) copy() (copied Settings) {
|
||||
System: s.System.copy(),
|
||||
Updater: s.Updater.copy(),
|
||||
Version: s.Version.copy(),
|
||||
VPN: s.VPN.copy(),
|
||||
VPN: s.VPN.Copy(),
|
||||
Pprof: s.Pprof.Copy(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,10 +95,11 @@ func (s *Settings) MergeWith(other Settings) {
|
||||
s.Updater.mergeWith(other.Updater)
|
||||
s.Version.mergeWith(other.Version)
|
||||
s.VPN.mergeWith(other.VPN)
|
||||
s.Pprof.MergeWith(other.Pprof)
|
||||
}
|
||||
|
||||
func (s *Settings) OverrideWith(other Settings,
|
||||
allServers models.AllServers) (err error) {
|
||||
storage Storage, ipv6Supported bool) (err error) {
|
||||
patchedSettings := s.copy()
|
||||
patchedSettings.ControlServer.overrideWith(other.ControlServer)
|
||||
patchedSettings.DNS.overrideWith(other.DNS)
|
||||
@@ -99,8 +112,9 @@ func (s *Settings) OverrideWith(other Settings,
|
||||
patchedSettings.System.overrideWith(other.System)
|
||||
patchedSettings.Updater.overrideWith(other.Updater)
|
||||
patchedSettings.Version.overrideWith(other.Version)
|
||||
patchedSettings.VPN.overrideWith(other.VPN)
|
||||
err = patchedSettings.Validate(allServers)
|
||||
patchedSettings.VPN.OverrideWith(other.VPN)
|
||||
patchedSettings.Pprof.OverrideWith(other.Pprof)
|
||||
err = patchedSettings.Validate(storage, ipv6Supported)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -118,9 +132,10 @@ func (s *Settings) SetDefaults() {
|
||||
s.PublicIP.setDefaults()
|
||||
s.Shadowsocks.setDefaults()
|
||||
s.System.setDefaults()
|
||||
s.Updater.SetDefaults()
|
||||
s.Version.setDefaults()
|
||||
s.VPN.setDefaults()
|
||||
s.Updater.SetDefaults(*s.VPN.Provider.Name)
|
||||
s.Pprof.SetDefaults()
|
||||
}
|
||||
|
||||
func (s Settings) String() string {
|
||||
@@ -142,6 +157,41 @@ func (s Settings) toLinesNode() (node *gotree.Node) {
|
||||
node.AppendNode(s.PublicIP.toLinesNode())
|
||||
node.AppendNode(s.Updater.toLinesNode())
|
||||
node.AppendNode(s.Version.toLinesNode())
|
||||
node.AppendNode(s.Pprof.ToLinesNode())
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
func (s Settings) Warnings() (warnings []string) {
|
||||
if *s.VPN.Provider.Name == providers.HideMyAss {
|
||||
warnings = append(warnings, "HideMyAss dropped support for Linux OpenVPN "+
|
||||
" so this will likely not work anymore. See https://github.com/qdm12/gluetun/issues/1498.")
|
||||
}
|
||||
|
||||
if helpers.IsOneOf(*s.VPN.Provider.Name, providers.SlickVPN) &&
|
||||
s.VPN.Type == vpn.OpenVPN {
|
||||
if s.VPN.OpenVPN.Version == openvpn.Openvpn24 {
|
||||
warnings = append(warnings, "OpenVPN 2.4 uses OpenSSL 1.1.1 "+
|
||||
"which allows the usage of weak security in today's standards. "+
|
||||
"This can be ok if good security is enforced by the VPN provider. "+
|
||||
"However, "+*s.VPN.Provider.Name+" uses weak security so you should use "+
|
||||
"OpenVPN 2.5 to enforce good security practices.")
|
||||
} else {
|
||||
warnings = append(warnings, "OpenVPN 2.5 uses OpenSSL 3 "+
|
||||
"which prohibits the usage of weak security in today's standards. "+
|
||||
*s.VPN.Provider.Name+" uses weak security which is out "+
|
||||
"of Gluetun's control so the only workaround is to allow such weaknesses "+
|
||||
`using the OpenVPN option tls-cipher "DEFAULT:@SECLEVEL=0". `+
|
||||
"You might want to reach to your provider so they upgrade their certificates. "+
|
||||
"Once this is done, you will have to let the Gluetun maintainers know "+
|
||||
"by creating an issue, attaching the new certificate and we will update Gluetun.")
|
||||
}
|
||||
}
|
||||
|
||||
if s.VPN.OpenVPN.Version == openvpn.Openvpn24 {
|
||||
warnings = append(warnings, "OpenVPN 2.4 will be removed in release v3.34.0 (around June 2023). "+
|
||||
"Please create an issue if you have a compelling reason to keep it.")
|
||||
}
|
||||
|
||||
return warnings
|
||||
}
|
||||
|
||||
@@ -34,7 +34,6 @@ func Test_Settings_String(t *testing.T) {
|
||||
| ├── User: [not set]
|
||||
| ├── Password: [not set]
|
||||
| ├── Private Internet Access encryption preset: strong
|
||||
| ├── Tunnel IPv6: no
|
||||
| ├── Network interface: tun0
|
||||
| ├── Run OpenVPN as: root
|
||||
| └── Verbosity level: 1
|
||||
@@ -66,7 +65,9 @@ func Test_Settings_String(t *testing.T) {
|
||||
| └── Log level: INFO
|
||||
├── Health settings:
|
||||
| ├── Server listening address: 127.0.0.1:9999
|
||||
| ├── Target address: github.com:443
|
||||
| ├── Target address: cloudflare.com:443
|
||||
| ├── Read header timeout: 100ms
|
||||
| ├── Read timeout: 500ms
|
||||
| └── VPN wait durations:
|
||||
| ├── Initial duration: 6s
|
||||
| └── Additional duration: 5s
|
||||
@@ -75,7 +76,7 @@ func Test_Settings_String(t *testing.T) {
|
||||
├── HTTP proxy settings:
|
||||
| └── Enabled: no
|
||||
├── Control server settings:
|
||||
| ├── Listening port: 8000
|
||||
| ├── Listening address: :8000
|
||||
| └── Logging: yes
|
||||
├── OS Alpine settings:
|
||||
| ├── Process UID: 1000
|
||||
|
||||
@@ -3,15 +3,14 @@ package settings
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/qdm12/gluetun/internal/provider/surfshark/servers"
|
||||
)
|
||||
|
||||
func surfsharkRetroRegion(selection ServerSelection) (
|
||||
updatedSelection ServerSelection) {
|
||||
locationData := constants.SurfsharkLocationData()
|
||||
locationData := servers.LocationData()
|
||||
|
||||
retroToLocation := make(map[string]models.SurfsharkLocationData, len(locationData))
|
||||
retroToLocation := make(map[string]servers.ServerLocation, len(locationData))
|
||||
for _, data := range locationData {
|
||||
if data.RetroLoc == "" {
|
||||
continue
|
||||
|
||||
@@ -7,8 +7,8 @@ import (
|
||||
|
||||
// System contains settings to configure system related elements.
|
||||
type System struct {
|
||||
PUID *uint16
|
||||
PGID *uint16
|
||||
PUID *uint32
|
||||
PGID *uint32
|
||||
Timezone string
|
||||
}
|
||||
|
||||
@@ -19,28 +19,28 @@ func (s System) validate() (err error) {
|
||||
|
||||
func (s *System) copy() (copied System) {
|
||||
return System{
|
||||
PUID: helpers.CopyUint16Ptr(s.PUID),
|
||||
PGID: helpers.CopyUint16Ptr(s.PGID),
|
||||
PUID: helpers.CopyUint32Ptr(s.PUID),
|
||||
PGID: helpers.CopyUint32Ptr(s.PGID),
|
||||
Timezone: s.Timezone,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *System) mergeWith(other System) {
|
||||
s.PUID = helpers.MergeWithUint16(s.PUID, other.PUID)
|
||||
s.PGID = helpers.MergeWithUint16(s.PGID, other.PGID)
|
||||
s.PUID = helpers.MergeWithUint32(s.PUID, other.PUID)
|
||||
s.PGID = helpers.MergeWithUint32(s.PGID, other.PGID)
|
||||
s.Timezone = helpers.MergeWithString(s.Timezone, other.Timezone)
|
||||
}
|
||||
|
||||
func (s *System) overrideWith(other System) {
|
||||
s.PUID = helpers.OverrideWithUint16(s.PUID, other.PUID)
|
||||
s.PGID = helpers.OverrideWithUint16(s.PGID, other.PGID)
|
||||
s.PUID = helpers.OverrideWithUint32(s.PUID, other.PUID)
|
||||
s.PGID = helpers.OverrideWithUint32(s.PGID, other.PGID)
|
||||
s.Timezone = helpers.OverrideWithString(s.Timezone, other.Timezone)
|
||||
}
|
||||
|
||||
func (s *System) setDefaults() {
|
||||
const defaultID = 1000
|
||||
s.PUID = helpers.DefaultUint16(s.PUID, defaultID)
|
||||
s.PGID = helpers.DefaultUint16(s.PGID, defaultID)
|
||||
s.PUID = helpers.DefaultUint32(s.PUID, defaultID)
|
||||
s.PGID = helpers.DefaultUint32(s.PGID, defaultID)
|
||||
}
|
||||
|
||||
func (s System) String() string {
|
||||
|
||||
@@ -2,12 +2,11 @@ package settings
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||
"github.com/qdm12/gotree"
|
||||
)
|
||||
|
||||
@@ -21,16 +20,15 @@ type Updater struct {
|
||||
Period *time.Duration
|
||||
// DNSAddress is the DNS server address to use
|
||||
// to resolve VPN server hostnames to IP addresses.
|
||||
// It cannot be nil in the internal state.
|
||||
DNSAddress net.IP
|
||||
// It cannot be the empty string in the internal state.
|
||||
DNSAddress string
|
||||
// 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
|
||||
// to update server information for.
|
||||
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) {
|
||||
@@ -40,21 +38,23 @@ func (u Updater) Validate() (err error) {
|
||||
ErrUpdaterPeriodTooSmall, *u.Period, minPeriod)
|
||||
}
|
||||
|
||||
for i, provider := range u.Providers {
|
||||
valid := false
|
||||
for _, validProvider := range constants.AllProviders() {
|
||||
if validProvider == constants.Custom {
|
||||
continue
|
||||
}
|
||||
if u.MinRatio <= 0 || u.MinRatio > 1 {
|
||||
return fmt.Errorf("%w: %.2f must be between 0+ and 1",
|
||||
ErrMinRatioNotValid, u.MinRatio)
|
||||
}
|
||||
|
||||
validProviders := providers.All()
|
||||
for _, provider := range u.Providers {
|
||||
valid := false
|
||||
for _, validProvider := range validProviders {
|
||||
if provider == validProvider {
|
||||
valid = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !valid {
|
||||
return fmt.Errorf("%w: %s at index %d",
|
||||
ErrVPNProviderNameNotValid, provider, i)
|
||||
return fmt.Errorf("%w: %q can only be one of %s",
|
||||
ErrVPNProviderNameNotValid, provider, helpers.ChoicesOrString(validProviders))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,35 +64,43 @@ func (u Updater) Validate() (err error) {
|
||||
func (u *Updater) copy() (copied Updater) {
|
||||
return Updater{
|
||||
Period: helpers.CopyDurationPtr(u.Period),
|
||||
DNSAddress: helpers.CopyIP(u.DNSAddress),
|
||||
DNSAddress: u.DNSAddress,
|
||||
MinRatio: u.MinRatio,
|
||||
Providers: helpers.CopyStringSlice(u.Providers),
|
||||
CLI: u.CLI,
|
||||
}
|
||||
}
|
||||
|
||||
// mergeWith merges the other settings into any
|
||||
// unset field of the receiver settings object.
|
||||
func (u *Updater) mergeWith(other Updater) {
|
||||
u.Period = helpers.MergeWithDuration(u.Period, other.Period)
|
||||
u.DNSAddress = helpers.MergeWithIP(u.DNSAddress, other.DNSAddress)
|
||||
u.Period = helpers.MergeWithDurationPtr(u.Period, other.Period)
|
||||
u.DNSAddress = helpers.MergeWithString(u.DNSAddress, other.DNSAddress)
|
||||
u.MinRatio = helpers.MergeWithFloat64(u.MinRatio, other.MinRatio)
|
||||
u.Providers = helpers.MergeStringSlices(u.Providers, other.Providers)
|
||||
u.CLI = helpers.MergeWithBool(u.CLI, other.CLI)
|
||||
}
|
||||
|
||||
// overrideWith overrides fields of the receiver
|
||||
// settings object with any field set in the other
|
||||
// settings.
|
||||
func (u *Updater) overrideWith(other Updater) {
|
||||
u.Period = helpers.OverrideWithDuration(u.Period, other.Period)
|
||||
u.DNSAddress = helpers.OverrideWithIP(u.DNSAddress, other.DNSAddress)
|
||||
u.Period = helpers.OverrideWithDurationPtr(u.Period, other.Period)
|
||||
u.DNSAddress = helpers.OverrideWithString(u.DNSAddress, other.DNSAddress)
|
||||
u.MinRatio = helpers.OverrideWithFloat64(u.MinRatio, other.MinRatio)
|
||||
u.Providers = helpers.OverrideWithStringSlice(u.Providers, other.Providers)
|
||||
u.CLI = helpers.MergeWithBool(u.CLI, other.CLI)
|
||||
}
|
||||
|
||||
func (u *Updater) SetDefaults() {
|
||||
u.Period = helpers.DefaultDuration(u.Period, 0)
|
||||
u.DNSAddress = helpers.DefaultIP(u.DNSAddress, net.IPv4(1, 1, 1, 1))
|
||||
u.CLI = helpers.DefaultBool(u.CLI, false)
|
||||
func (u *Updater) SetDefaults(vpnProvider string) {
|
||||
u.Period = helpers.DefaultDurationPtr(u.Period, 0)
|
||||
u.DNSAddress = helpers.DefaultString(u.DNSAddress, "1.1.1.1:53")
|
||||
|
||||
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 {
|
||||
@@ -100,18 +108,15 @@ func (u Updater) String() string {
|
||||
}
|
||||
|
||||
func (u Updater) toLinesNode() (node *gotree.Node) {
|
||||
if *u.Period == 0 {
|
||||
if *u.Period == 0 || len(u.Providers) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
node = gotree.New("Server data updater settings:")
|
||||
node.Appendf("Update period: %s", *u.Period)
|
||||
node.Appendf("DNS address: %s", u.DNSAddress)
|
||||
node.Appendf("Minimum ratio: %.1f", u.MinRatio)
|
||||
node.Appendf("Providers to update: %s", strings.Join(u.Providers, ", "))
|
||||
|
||||
if *u.CLI {
|
||||
node.Appendf("CLI mode: enabled")
|
||||
}
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
129
internal/configuration/settings/validation/servers.go
Normal file
129
internal/configuration/settings/validation/servers.go
Normal file
@@ -0,0 +1,129 @@
|
||||
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
|
||||
}
|
||||
21
internal/configuration/settings/validation/surfshark.go
Normal file
21
internal/configuration/settings/validation/surfshark.go
Normal file
@@ -0,0 +1,21 @@
|
||||
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,8 +5,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/qdm12/gluetun/internal/constants/vpn"
|
||||
"github.com/qdm12/gotree"
|
||||
)
|
||||
|
||||
@@ -21,35 +20,35 @@ type VPN struct {
|
||||
}
|
||||
|
||||
// TODO v4 remove pointer for receiver (because of Surfshark).
|
||||
func (v *VPN) validate(allServers models.AllServers) (err error) {
|
||||
func (v *VPN) Validate(storage Storage, ipv6Supported bool) (err error) {
|
||||
// Validate Type
|
||||
validVPNTypes := []string{constants.OpenVPN, constants.Wireguard}
|
||||
validVPNTypes := []string{vpn.OpenVPN, vpn.Wireguard}
|
||||
if !helpers.IsOneOf(v.Type, validVPNTypes...) {
|
||||
return fmt.Errorf("%w: %q and can only be one of %s",
|
||||
ErrVPNTypeNotValid, v.Type, strings.Join(validVPNTypes, ", "))
|
||||
}
|
||||
|
||||
err = v.Provider.validate(v.Type, allServers)
|
||||
err = v.Provider.validate(v.Type, storage)
|
||||
if err != nil {
|
||||
return fmt.Errorf("provider settings validation failed: %w", err)
|
||||
return fmt.Errorf("provider settings: %w", err)
|
||||
}
|
||||
|
||||
if v.Type == constants.OpenVPN {
|
||||
if v.Type == vpn.OpenVPN {
|
||||
err := v.OpenVPN.validate(*v.Provider.Name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("OpenVPN settings validation failed: %w", err)
|
||||
return fmt.Errorf("OpenVPN settings: %w", err)
|
||||
}
|
||||
} else {
|
||||
err := v.Wireguard.validate(*v.Provider.Name)
|
||||
err := v.Wireguard.validate(*v.Provider.Name, ipv6Supported)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Wireguard settings validation failed: %w", err)
|
||||
return fmt.Errorf("Wireguard settings: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *VPN) copy() (copied VPN) {
|
||||
func (v *VPN) Copy() (copied VPN) {
|
||||
return VPN{
|
||||
Type: v.Type,
|
||||
Provider: v.Provider.copy(),
|
||||
@@ -65,7 +64,7 @@ func (v *VPN) mergeWith(other VPN) {
|
||||
v.Wireguard.mergeWith(other.Wireguard)
|
||||
}
|
||||
|
||||
func (v *VPN) overrideWith(other VPN) {
|
||||
func (v *VPN) OverrideWith(other VPN) {
|
||||
v.Type = helpers.OverrideWithString(v.Type, other.Type)
|
||||
v.Provider.overrideWith(other.Provider)
|
||||
v.OpenVPN.overrideWith(other.OpenVPN)
|
||||
@@ -73,7 +72,7 @@ func (v *VPN) overrideWith(other VPN) {
|
||||
}
|
||||
|
||||
func (v *VPN) setDefaults() {
|
||||
v.Type = helpers.DefaultString(v.Type, constants.OpenVPN)
|
||||
v.Type = helpers.DefaultString(v.Type, vpn.OpenVPN)
|
||||
v.Provider.setDefaults()
|
||||
v.OpenVPN.setDefaults(*v.Provider.Name)
|
||||
v.Wireguard.setDefaults()
|
||||
@@ -88,7 +87,7 @@ func (v VPN) toLinesNode() (node *gotree.Node) {
|
||||
|
||||
node.AppendNode(v.Provider.toLinesNode())
|
||||
|
||||
if v.Type == constants.OpenVPN {
|
||||
if v.Type == vpn.OpenVPN {
|
||||
node.AppendNode(v.OpenVPN.toLinesNode())
|
||||
} else {
|
||||
node.AppendNode(v.Wireguard.toLinesNode())
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"regexp"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||
"github.com/qdm12/gotree"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
)
|
||||
@@ -27,18 +27,24 @@ type Wireguard struct {
|
||||
// to create. It cannot be the empty string in the
|
||||
// internal state.
|
||||
Interface string
|
||||
// Implementation is the Wireguard implementation to use.
|
||||
// It can be "auto", "userspace" or "kernelspace".
|
||||
// It defaults to "auto" and cannot be the empty string
|
||||
// in the internal state.
|
||||
Implementation string
|
||||
}
|
||||
|
||||
var regexpInterfaceName = regexp.MustCompile(`^[a-zA-Z0-9_]+$`)
|
||||
|
||||
// Validate validates Wireguard settings.
|
||||
// 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, ipv6Supported bool) (err error) {
|
||||
if !helpers.IsOneOf(vpnProvider,
|
||||
constants.Custom,
|
||||
constants.Ivpn,
|
||||
constants.Mullvad,
|
||||
constants.Windscribe,
|
||||
providers.Custom,
|
||||
providers.Ivpn,
|
||||
providers.Mullvad,
|
||||
providers.Surfshark,
|
||||
providers.Windscribe,
|
||||
) {
|
||||
// do not validate for VPN provider not supporting Wireguard
|
||||
return nil
|
||||
@@ -50,14 +56,20 @@ func (w Wireguard) validate(vpnProvider string) (err error) {
|
||||
}
|
||||
_, err = wgtypes.ParseKey(*w.PrivateKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %s", ErrWireguardPrivateKeyNotValid, err)
|
||||
return fmt.Errorf("private key is not valid: %w", err)
|
||||
}
|
||||
|
||||
if vpnProvider == providers.Airvpn {
|
||||
if *w.PreSharedKey == "" {
|
||||
return fmt.Errorf("%w", ErrWireguardPreSharedKeyNotSet)
|
||||
}
|
||||
}
|
||||
|
||||
// Validate PreSharedKey
|
||||
if *w.PreSharedKey != "" { // Note: this is optional
|
||||
_, err = wgtypes.ParseKey(*w.PreSharedKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %s", ErrWireguardPreSharedKeyNotValid, err)
|
||||
return fmt.Errorf("pre-shared key is not valid: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,6 +82,12 @@ func (w Wireguard) validate(vpnProvider string) (err error) {
|
||||
return fmt.Errorf("%w: for address at index %d: %s",
|
||||
ErrWireguardInterfaceAddressNotSet, i, ipNet.String())
|
||||
}
|
||||
|
||||
ipv6Net := ipNet.IP.To4() == nil
|
||||
if ipv6Net && !ipv6Supported {
|
||||
return fmt.Errorf("%w: address %s",
|
||||
ErrWireguardInterfaceAddressIPv6, ipNet)
|
||||
}
|
||||
}
|
||||
|
||||
// Validate interface
|
||||
@@ -78,15 +96,22 @@ func (w Wireguard) validate(vpnProvider string) (err error) {
|
||||
ErrWireguardInterfaceNotValid, w.Interface, regexpInterfaceName)
|
||||
}
|
||||
|
||||
validImplementations := []string{"auto", "userspace", "kernelspace"}
|
||||
if !helpers.IsOneOf(w.Implementation, validImplementations...) {
|
||||
return fmt.Errorf("%w: %s must be one of %s", ErrWireguardImplementationNotValid,
|
||||
w.Implementation, helpers.ChoicesOrString(validImplementations))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *Wireguard) copy() (copied Wireguard) {
|
||||
return Wireguard{
|
||||
PrivateKey: helpers.CopyStringPtr(w.PrivateKey),
|
||||
PreSharedKey: helpers.CopyStringPtr(w.PreSharedKey),
|
||||
Addresses: helpers.CopyIPNetSlice(w.Addresses),
|
||||
Interface: w.Interface,
|
||||
PrivateKey: helpers.CopyStringPtr(w.PrivateKey),
|
||||
PreSharedKey: helpers.CopyStringPtr(w.PreSharedKey),
|
||||
Addresses: helpers.CopyIPNetSlice(w.Addresses),
|
||||
Interface: w.Interface,
|
||||
Implementation: w.Implementation,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,6 +120,7 @@ func (w *Wireguard) mergeWith(other Wireguard) {
|
||||
w.PreSharedKey = helpers.MergeWithStringPtr(w.PreSharedKey, other.PreSharedKey)
|
||||
w.Addresses = helpers.MergeIPNetsSlices(w.Addresses, other.Addresses)
|
||||
w.Interface = helpers.MergeWithString(w.Interface, other.Interface)
|
||||
w.Implementation = helpers.MergeWithString(w.Implementation, other.Implementation)
|
||||
}
|
||||
|
||||
func (w *Wireguard) overrideWith(other Wireguard) {
|
||||
@@ -102,12 +128,14 @@ func (w *Wireguard) overrideWith(other Wireguard) {
|
||||
w.PreSharedKey = helpers.OverrideWithStringPtr(w.PreSharedKey, other.PreSharedKey)
|
||||
w.Addresses = helpers.OverrideWithIPNetsSlice(w.Addresses, other.Addresses)
|
||||
w.Interface = helpers.OverrideWithString(w.Interface, other.Interface)
|
||||
w.Implementation = helpers.OverrideWithString(w.Implementation, other.Implementation)
|
||||
}
|
||||
|
||||
func (w *Wireguard) setDefaults() {
|
||||
w.PrivateKey = helpers.DefaultStringPtr(w.PrivateKey, "")
|
||||
w.PreSharedKey = helpers.DefaultStringPtr(w.PreSharedKey, "")
|
||||
w.Interface = helpers.DefaultString(w.Interface, "wg0")
|
||||
w.Implementation = helpers.DefaultString(w.Implementation, "auto")
|
||||
}
|
||||
|
||||
func (w Wireguard) String() string {
|
||||
@@ -134,5 +162,9 @@ func (w Wireguard) toLinesNode() (node *gotree.Node) {
|
||||
|
||||
node.Appendf("Network interface: %s", w.Interface)
|
||||
|
||||
if w.Implementation != "auto" {
|
||||
node.Appendf("Implementation: %s", w.Implementation)
|
||||
}
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"net"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||
"github.com/qdm12/gotree"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
)
|
||||
@@ -19,7 +19,7 @@ type WireguardSelection struct {
|
||||
// in the internal state.
|
||||
EndpointIP net.IP
|
||||
// EndpointPort is a the server port to use for the VPN server.
|
||||
// It is optional for VPN providers IVPN, Mullvad
|
||||
// It is optional for VPN providers IVPN, Mullvad, Surfshark
|
||||
// and Windscribe, and compulsory for the others.
|
||||
// When optional, it can be set to 0 to indicate not use
|
||||
// a custom endpoint port. It cannot be nil in the internal
|
||||
@@ -36,8 +36,10 @@ type WireguardSelection struct {
|
||||
func (w WireguardSelection) validate(vpnProvider string) (err error) {
|
||||
// Validate EndpointIP
|
||||
switch vpnProvider {
|
||||
case constants.Ivpn, constants.Mullvad, constants.Windscribe: // endpoint IP addresses are baked in
|
||||
case constants.Custom:
|
||||
case providers.Airvpn, providers.Ivpn, providers.Mullvad,
|
||||
providers.Surfshark, providers.Windscribe:
|
||||
// endpoint IP addresses are baked in
|
||||
case providers.Custom:
|
||||
if len(w.EndpointIP) == 0 {
|
||||
return ErrWireguardEndpointIPNotSet
|
||||
}
|
||||
@@ -47,23 +49,30 @@ func (w WireguardSelection) validate(vpnProvider string) (err error) {
|
||||
// Validate EndpointPort
|
||||
switch vpnProvider {
|
||||
// EndpointPort is required
|
||||
case constants.Custom:
|
||||
case providers.Custom:
|
||||
if *w.EndpointPort == 0 {
|
||||
return ErrWireguardEndpointPortNotSet
|
||||
}
|
||||
case constants.Ivpn, constants.Mullvad, constants.Windscribe:
|
||||
// EndpointPort cannot be set
|
||||
case providers.Surfshark:
|
||||
if *w.EndpointPort != 0 {
|
||||
return ErrWireguardEndpointPortSet
|
||||
}
|
||||
case providers.Airvpn, providers.Ivpn, providers.Mullvad, providers.Windscribe:
|
||||
// EndpointPort is optional and can be 0
|
||||
if *w.EndpointPort == 0 {
|
||||
break // no custom endpoint port set
|
||||
}
|
||||
if vpnProvider == constants.Mullvad {
|
||||
if vpnProvider == providers.Mullvad {
|
||||
break // no restriction on custom endpoint port value
|
||||
}
|
||||
var allowed []uint16
|
||||
switch vpnProvider {
|
||||
case constants.Ivpn:
|
||||
case providers.Airvpn:
|
||||
allowed = []uint16{1637, 47107}
|
||||
case providers.Ivpn:
|
||||
allowed = []uint16{2049, 2050, 53, 30587, 41893, 48574, 58237}
|
||||
case constants.Windscribe:
|
||||
case providers.Windscribe:
|
||||
allowed = []uint16{53, 80, 123, 443, 1194, 65142}
|
||||
}
|
||||
|
||||
@@ -78,8 +87,10 @@ func (w WireguardSelection) validate(vpnProvider string) (err error) {
|
||||
|
||||
// Validate PublicKey
|
||||
switch vpnProvider {
|
||||
case constants.Ivpn, constants.Mullvad, constants.Windscribe: // public keys are baked in
|
||||
case constants.Custom:
|
||||
case providers.Ivpn, providers.Mullvad,
|
||||
providers.Surfshark, providers.Windscribe:
|
||||
// public keys are baked in
|
||||
case providers.Custom:
|
||||
if w.PublicKey == "" {
|
||||
return ErrWireguardPublicKeyNotSet
|
||||
}
|
||||
|
||||
23
internal/configuration/sources/env/dns.go
vendored
23
internal/configuration/sources/env/dns.go
vendored
@@ -3,13 +3,12 @@ package env
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
)
|
||||
|
||||
func (r *Reader) readDNS() (dns settings.DNS, err error) {
|
||||
dns.ServerAddress, err = r.readDNSServerAddress()
|
||||
func (s *Source) readDNS() (dns settings.DNS, err error) {
|
||||
dns.ServerAddress, err = s.readDNSServerAddress()
|
||||
if err != nil {
|
||||
return dns, err
|
||||
}
|
||||
@@ -19,30 +18,30 @@ func (r *Reader) readDNS() (dns settings.DNS, err error) {
|
||||
return dns, fmt.Errorf("environment variable DNS_KEEP_NAMESERVER: %w", err)
|
||||
}
|
||||
|
||||
dns.DoT, err = r.readDoT()
|
||||
dns.DoT, err = s.readDoT()
|
||||
if err != nil {
|
||||
return dns, fmt.Errorf("cannot read DoT settings: %w", err)
|
||||
return dns, fmt.Errorf("DoT settings: %w", err)
|
||||
}
|
||||
|
||||
return dns, nil
|
||||
}
|
||||
|
||||
func (r *Reader) readDNSServerAddress() (address net.IP, err error) {
|
||||
s := os.Getenv("DNS_PLAINTEXT_ADDRESS")
|
||||
if s == "" {
|
||||
func (s *Source) readDNSServerAddress() (address net.IP, err error) {
|
||||
key, value := s.getEnvWithRetro("DNS_ADDRESS", "DNS_PLAINTEXT_ADDRESS")
|
||||
if value == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
address = net.ParseIP(s)
|
||||
address = net.ParseIP(value)
|
||||
if address == nil {
|
||||
return nil, fmt.Errorf("environment variable DNS_PLAINTEXT_ADDRESS: %w: %s", ErrIPAddressParse, s)
|
||||
return nil, fmt.Errorf("environment variable %s: %w: %s", key, ErrIPAddressParse, value)
|
||||
}
|
||||
|
||||
// TODO remove in v4
|
||||
if !address.Equal(net.IPv4(127, 0, 0, 1)) { //nolint:gomnd
|
||||
r.warner.Warn("DNS_PLAINTEXT_ADDRESS is set to " + s +
|
||||
s.warner.Warn(key + " is set to " + value +
|
||||
" 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 serves." +
|
||||
" If the DoT server fails to start, the IPv4 address of the first plaintext DNS server" +
|
||||
" corresponding to the first DoT provider chosen is used.")
|
||||
}
|
||||
|
||||
@@ -5,18 +5,19 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/govalid/binary"
|
||||
"inet.af/netaddr"
|
||||
)
|
||||
|
||||
func (r *Reader) readDNSBlacklist() (blacklist settings.DNSBlacklist, err error) {
|
||||
func (s *Source) readDNSBlacklist() (blacklist settings.DNSBlacklist, err error) {
|
||||
blacklist.BlockMalicious, err = envToBoolPtr("BLOCK_MALICIOUS")
|
||||
if err != nil {
|
||||
return blacklist, fmt.Errorf("environment variable BLOCK_MALICIOUS: %w", err)
|
||||
}
|
||||
|
||||
blacklist.BlockSurveillance, err = r.readBlockSurveillance()
|
||||
blacklist.BlockSurveillance, err = s.readBlockSurveillance()
|
||||
if err != nil {
|
||||
return blacklist, fmt.Errorf("environment variable BLOCK_MALICIOUS: %w", err)
|
||||
return blacklist, err
|
||||
}
|
||||
|
||||
blacklist.BlockAds, err = envToBoolPtr("BLOCK_ADS")
|
||||
@@ -35,23 +36,19 @@ func (r *Reader) readDNSBlacklist() (blacklist settings.DNSBlacklist, err error)
|
||||
return blacklist, nil
|
||||
}
|
||||
|
||||
func (r *Reader) readBlockSurveillance() (blocked *bool, err error) {
|
||||
blocked, err = envToBoolPtr("BLOCK_SURVEILLANCE")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("environment variable BLOCK_SURVEILLANCE: %w", err)
|
||||
} else if blocked != nil {
|
||||
return blocked, nil
|
||||
func (s *Source) readBlockSurveillance() (blocked *bool, err error) {
|
||||
key, value := s.getEnvWithRetro("BLOCK_SURVEILLANCE", "BLOCK_NSA")
|
||||
if value == "" {
|
||||
return nil, nil //nolint:nilnil
|
||||
}
|
||||
|
||||
blocked, err = envToBoolPtr("BLOCK_NSA")
|
||||
blocked = new(bool)
|
||||
*blocked, err = binary.Validate(value)
|
||||
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, fmt.Errorf("environment variable %s: %w", key, err)
|
||||
}
|
||||
|
||||
return nil, nil //nolint:nilnil
|
||||
return blocked, nil
|
||||
}
|
||||
|
||||
var (
|
||||
|
||||
4
internal/configuration/sources/env/dot.go
vendored
4
internal/configuration/sources/env/dot.go
vendored
@@ -6,7 +6,7 @@ import (
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
)
|
||||
|
||||
func (r *Reader) readDoT() (dot settings.DoT, err error) {
|
||||
func (s *Source) readDoT() (dot settings.DoT, err error) {
|
||||
dot.Enabled, err = envToBoolPtr("DOT")
|
||||
if err != nil {
|
||||
return dot, fmt.Errorf("environment variable DOT: %w", err)
|
||||
@@ -22,7 +22,7 @@ func (r *Reader) readDoT() (dot settings.DoT, err error) {
|
||||
return dot, err
|
||||
}
|
||||
|
||||
dot.Blacklist, err = r.readDNSBlacklist()
|
||||
dot.Blacklist, err = s.readDNSBlacklist()
|
||||
if err != nil {
|
||||
return dot, err
|
||||
}
|
||||
|
||||
22
internal/configuration/sources/env/firewall.go
vendored
22
internal/configuration/sources/env/firewall.go
vendored
@@ -9,7 +9,7 @@ import (
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
)
|
||||
|
||||
func (r *Reader) readFirewall() (firewall settings.Firewall, err error) {
|
||||
func (s *Source) readFirewall() (firewall settings.Firewall, err error) {
|
||||
vpnInputPortStrings := envToCSV("FIREWALL_VPN_INPUT_PORTS")
|
||||
firewall.VPNInputPorts, err = stringsToPorts(vpnInputPortStrings)
|
||||
if err != nil {
|
||||
@@ -22,16 +22,8 @@ func (r *Reader) readFirewall() (firewall settings.Firewall, err error) {
|
||||
return firewall, fmt.Errorf("environment variable FIREWALL_INPUT_PORTS: %w", err)
|
||||
}
|
||||
|
||||
outboundSubnetsKey := "FIREWALL_OUTBOUND_SUBNETS"
|
||||
outboundSubnetsKey, _ := s.getEnvWithRetro("FIREWALL_OUTBOUND_SUBNETS", "EXTRA_SUBNETS")
|
||||
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)
|
||||
if err != nil {
|
||||
return firewall, fmt.Errorf("environment variable %s: %w", outboundSubnetsKey, err)
|
||||
@@ -63,8 +55,7 @@ func stringsToPorts(ss []string) (ports []uint16, err error) {
|
||||
for i, s := range ss {
|
||||
port, err := strconv.Atoi(s)
|
||||
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 {
|
||||
return nil, fmt.Errorf("%w: must be between 1 and 65535: %d",
|
||||
ErrPortValue, port)
|
||||
@@ -74,10 +65,6 @@ func stringsToPorts(ss []string) (ports []uint16, err error) {
|
||||
return ports, nil
|
||||
}
|
||||
|
||||
var (
|
||||
ErrIPNetParsing = errors.New("cannot parse IP network")
|
||||
)
|
||||
|
||||
func stringsToIPNets(ss []string) (ipNets []net.IPNet, err error) {
|
||||
if len(ss) == 0 {
|
||||
return nil, nil
|
||||
@@ -86,8 +73,7 @@ func stringsToIPNets(ss []string) (ipNets []net.IPNet, err error) {
|
||||
for i, s := range ss {
|
||||
ip, ipNet, err := net.ParseCIDR(s)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %s: %s",
|
||||
ErrIPNetParsing, s, err)
|
||||
return nil, fmt.Errorf("parsing IP network %q: %w", s, err)
|
||||
}
|
||||
ipNet.IP = ip
|
||||
ipNets[i] = *ipNet
|
||||
|
||||
33
internal/configuration/sources/env/health.go
vendored
33
internal/configuration/sources/env/health.go
vendored
@@ -2,27 +2,23 @@ package env
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
)
|
||||
|
||||
func (r *Reader) ReadHealth() (health settings.Health, err error) {
|
||||
health.ServerAddress = os.Getenv("HEALTH_SERVER_ADDRESS")
|
||||
health.TargetAddress = os.Getenv("HEALTH_ADDRESS_TO_PING")
|
||||
if health.TargetAddress == "" {
|
||||
health.TargetAddress = os.Getenv("HEALTH_TARGET_ADDRESS")
|
||||
}
|
||||
func (s *Source) ReadHealth() (health settings.Health, err error) {
|
||||
health.ServerAddress = getCleanedEnv("HEALTH_SERVER_ADDRESS")
|
||||
_, health.TargetAddress = s.getEnvWithRetro("HEALTH_TARGET_ADDRESS", "HEALTH_ADDRESS_TO_PING")
|
||||
|
||||
health.VPN.Initial, err = r.readDurationWithRetro(
|
||||
health.VPN.Initial, err = s.readDurationWithRetro(
|
||||
"HEALTH_VPN_DURATION_INITIAL",
|
||||
"HEALTH_OPENVPN_DURATION_INITIAL")
|
||||
if err != nil {
|
||||
return health, err
|
||||
}
|
||||
|
||||
health.VPN.Initial, err = r.readDurationWithRetro(
|
||||
health.VPN.Addition, err = s.readDurationWithRetro(
|
||||
"HEALTH_VPN_DURATION_ADDITION",
|
||||
"HEALTH_OPENVPN_DURATION_ADDITION")
|
||||
if err != nil {
|
||||
@@ -32,23 +28,16 @@ func (r *Reader) ReadHealth() (health settings.Health, err error) {
|
||||
return health, nil
|
||||
}
|
||||
|
||||
func (r *Reader) readDurationWithRetro(envKey, retroEnvKey string) (d *time.Duration, err error) {
|
||||
s := os.Getenv(envKey)
|
||||
if s == "" {
|
||||
s = os.Getenv(retroEnvKey)
|
||||
if s == "" {
|
||||
return nil, nil //nolint:nilnil
|
||||
}
|
||||
r.onRetroActive(envKey, retroEnvKey)
|
||||
envKey = retroEnvKey
|
||||
func (s *Source) readDurationWithRetro(envKey, retroEnvKey string) (d *time.Duration, err error) {
|
||||
envKey, value := s.getEnvWithRetro(envKey, retroEnvKey)
|
||||
if value == "" {
|
||||
return nil, nil //nolint:nilnil
|
||||
}
|
||||
|
||||
d = new(time.Duration)
|
||||
*d, err = time.ParseDuration(s)
|
||||
*d, err = time.ParseDuration(value)
|
||||
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
|
||||
|
||||
58
internal/configuration/sources/env/helpers.go
vendored
58
internal/configuration/sources/env/helpers.go
vendored
@@ -1,8 +1,6 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
@@ -13,16 +11,43 @@ import (
|
||||
"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) {
|
||||
csv := os.Getenv(envKey)
|
||||
csv := getCleanedEnv(envKey)
|
||||
if csv == "" {
|
||||
return nil
|
||||
}
|
||||
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) {
|
||||
s := os.Getenv(envKey)
|
||||
s := getCleanedEnv(envKey)
|
||||
if s == "" {
|
||||
return nil
|
||||
}
|
||||
@@ -30,7 +55,7 @@ func envToStringPtr(envKey string) (stringPtr *string) {
|
||||
}
|
||||
|
||||
func envToBoolPtr(envKey string) (boolPtr *bool, err error) {
|
||||
s := os.Getenv(envKey)
|
||||
s := getCleanedEnv(envKey)
|
||||
if s == "" {
|
||||
return nil, nil //nolint:nilnil
|
||||
}
|
||||
@@ -42,7 +67,7 @@ func envToBoolPtr(envKey string) (boolPtr *bool, err error) {
|
||||
}
|
||||
|
||||
func envToIntPtr(envKey string) (intPtr *int, err error) {
|
||||
s := os.Getenv(envKey)
|
||||
s := getCleanedEnv(envKey)
|
||||
if s == "" {
|
||||
return nil, nil //nolint:nilnil
|
||||
}
|
||||
@@ -54,7 +79,7 @@ func envToIntPtr(envKey string) (intPtr *int, err error) {
|
||||
}
|
||||
|
||||
func envToUint8Ptr(envKey string) (uint8Ptr *uint8, err error) {
|
||||
s := os.Getenv(envKey)
|
||||
s := getCleanedEnv(envKey)
|
||||
if s == "" {
|
||||
return nil, nil //nolint:nilnil
|
||||
}
|
||||
@@ -71,7 +96,7 @@ func envToUint8Ptr(envKey string) (uint8Ptr *uint8, err error) {
|
||||
}
|
||||
|
||||
func envToUint16Ptr(envKey string) (uint16Ptr *uint16, err error) {
|
||||
s := os.Getenv(envKey)
|
||||
s := getCleanedEnv(envKey)
|
||||
if s == "" {
|
||||
return nil, nil //nolint:nilnil
|
||||
}
|
||||
@@ -88,7 +113,7 @@ func envToUint16Ptr(envKey string) (uint16Ptr *uint16, err error) {
|
||||
}
|
||||
|
||||
func envToDurationPtr(envKey string) (durationPtr *time.Duration, err error) {
|
||||
s := os.Getenv(envKey)
|
||||
s := getCleanedEnv(envKey)
|
||||
if s == "" {
|
||||
return nil, nil //nolint:nilnil
|
||||
}
|
||||
@@ -107,28 +132,17 @@ func lowerAndSplit(csv string) (values []string) {
|
||||
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) {
|
||||
newErr = err
|
||||
for _, envKey := range envKeys {
|
||||
unsetErr := os.Unsetenv(envKey)
|
||||
if unsetErr != nil && newErr == nil {
|
||||
newErr = fmt.Errorf("cannot unset environment variable %s: %w", envKey, unsetErr)
|
||||
newErr = fmt.Errorf("unsetting environment variable %s: %w", envKey, unsetErr)
|
||||
}
|
||||
}
|
||||
return newErr
|
||||
}
|
||||
|
||||
func stringPtr(s string) *string { return &s }
|
||||
func uint16Ptr(n uint16) *uint16 { return &n }
|
||||
func uint32Ptr(n uint32) *uint32 { return &n }
|
||||
func boolPtr(b bool) *bool { return &b }
|
||||
|
||||
161
internal/configuration/sources/env/httproxy.go
vendored
161
internal/configuration/sources/env/httproxy.go
vendored
@@ -2,19 +2,17 @@ package env
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/govalid/binary"
|
||||
)
|
||||
|
||||
func (r *Reader) readHTTPProxy() (httpProxy settings.HTTPProxy, err error) {
|
||||
httpProxy.User = r.readHTTProxyUser()
|
||||
httpProxy.Password = r.readHTTProxyPassword()
|
||||
httpProxy.ListeningAddress = r.readHTTProxyListeningAddress()
|
||||
func (s *Source) readHTTPProxy() (httpProxy settings.HTTPProxy, err error) {
|
||||
httpProxy.User = s.readHTTProxyUser()
|
||||
httpProxy.Password = s.readHTTProxyPassword()
|
||||
httpProxy.ListeningAddress = s.readHTTProxyListeningAddress()
|
||||
|
||||
httpProxy.Enabled, err = r.readHTTProxyEnabled()
|
||||
httpProxy.Enabled, err = s.readHTTProxyEnabled()
|
||||
if err != nil {
|
||||
return httpProxy, err
|
||||
}
|
||||
@@ -24,7 +22,7 @@ func (r *Reader) readHTTPProxy() (httpProxy settings.HTTPProxy, err error) {
|
||||
return httpProxy, fmt.Errorf("environment variable HTTPPROXY_STEALTH: %w", err)
|
||||
}
|
||||
|
||||
httpProxy.Log, err = r.readHTTProxyLog()
|
||||
httpProxy.Log, err = s.readHTTProxyLog()
|
||||
if err != nil {
|
||||
return httpProxy, err
|
||||
}
|
||||
@@ -32,139 +30,62 @@ func (r *Reader) readHTTPProxy() (httpProxy settings.HTTPProxy, err error) {
|
||||
return httpProxy, nil
|
||||
}
|
||||
|
||||
func (r *Reader) readHTTProxyUser() (user *string) {
|
||||
s := os.Getenv("HTTPPROXY_USER")
|
||||
if s != "" {
|
||||
return &s
|
||||
func (s *Source) readHTTProxyUser() (user *string) {
|
||||
_, value := s.getEnvWithRetro("HTTPPROXY_USER", "PROXY_USER", "TINYPROXY_USER")
|
||||
if value != "" {
|
||||
return &value
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
func (r *Reader) readHTTProxyPassword() (user *string) {
|
||||
s := os.Getenv("HTTPPROXY_PASSWORD")
|
||||
if s != "" {
|
||||
return &s
|
||||
func (s *Source) readHTTProxyPassword() (user *string) {
|
||||
_, value := s.getEnvWithRetro("HTTPPROXY_PASSWORD", "PROXY_PASSWORD", "TINYPROXY_PASSWORD")
|
||||
if value != "" {
|
||||
return &value
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
func (r *Reader) readHTTProxyListeningAddress() (listeningAddress string) {
|
||||
// Retro-compatibility
|
||||
retroKeys := []string{"PROXY_PORT", "TINYPROXY_PORT", "HTTPPROXY_PORT"}
|
||||
for _, retroKey := range retroKeys {
|
||||
s := os.Getenv(retroKey)
|
||||
if s != "" {
|
||||
r.onRetroActive(retroKey, "HTTPPROXY_LISTENING_ADDRESS")
|
||||
return ":" + s
|
||||
}
|
||||
func (s *Source) readHTTProxyListeningAddress() (listeningAddress string) {
|
||||
key, value := s.getEnvWithRetro("HTTPPROXY_LISTENING_ADDRESS", "PROXY_PORT", "TINYPROXY_PORT", "HTTPPROXY_PORT")
|
||||
if key == "HTTPPROXY_LISTENING_ADDRESS" {
|
||||
return value
|
||||
}
|
||||
|
||||
return os.Getenv("HTTPPROXY_LISTENING_ADDRESS")
|
||||
return ":" + value
|
||||
}
|
||||
|
||||
func (r *Reader) readHTTProxyEnabled() (enabled *bool, err error) {
|
||||
s := strings.ToLower(os.Getenv("HTTPPROXY"))
|
||||
if s != "" {
|
||||
enabled = new(bool)
|
||||
*enabled, err = binary.Validate(s)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("environment variable HTTPPROXY: %w", err)
|
||||
}
|
||||
return enabled, nil
|
||||
func (s *Source) readHTTProxyEnabled() (enabled *bool, err error) {
|
||||
key, value := s.getEnvWithRetro("HTTPPROXY", "PROXY", "TINYPROXY")
|
||||
if value == "" {
|
||||
return nil, nil //nolint:nilnil
|
||||
}
|
||||
|
||||
// Retro-compatibility
|
||||
s = strings.ToLower(os.Getenv("TINYPROXY"))
|
||||
if s != "" {
|
||||
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
|
||||
enabled = new(bool)
|
||||
*enabled, err = binary.Validate(value)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("environment variable %s: %w", key, err)
|
||||
}
|
||||
|
||||
// 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
|
||||
return enabled, nil
|
||||
}
|
||||
|
||||
func (r *Reader) readHTTProxyLog() (enabled *bool, err error) {
|
||||
s := strings.ToLower(os.Getenv("HTTPPROXY_LOG"))
|
||||
if s != "" {
|
||||
enabled = new(bool)
|
||||
*enabled, err = binary.Validate(s)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("environment variable HTTPPROXY_LOG: %w", err)
|
||||
}
|
||||
return enabled, nil
|
||||
func (s *Source) readHTTProxyLog() (enabled *bool, err error) {
|
||||
key, value := s.getEnvWithRetro("HTTPPROXY_LOG", "PROXY_LOG_LEVEL", "TINYPROXY_LOG")
|
||||
if value == "" {
|
||||
return nil, nil //nolint:nilnil
|
||||
}
|
||||
|
||||
// Retro-compatibility
|
||||
retroOption := binary.OptionEnabled("on", "info", "connect", "notice")
|
||||
s = strings.ToLower(os.Getenv("TINYPROXY_LOG"))
|
||||
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
|
||||
var binaryOptions []binary.Option
|
||||
if key != "HTTPROXY_LOG" {
|
||||
retroOption := binary.OptionEnabled("on", "info", "connect", "notice")
|
||||
binaryOptions = append(binaryOptions, retroOption)
|
||||
}
|
||||
|
||||
// Retro-compatibility
|
||||
s = strings.ToLower(os.Getenv("PROXY_LOG_LEVEL"))
|
||||
if s != "" {
|
||||
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
|
||||
enabled = new(bool)
|
||||
*enabled, err = binary.Validate(value, binaryOptions...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("environment variable %s: %w", key, err)
|
||||
}
|
||||
|
||||
return nil, nil //nolint:nilnil
|
||||
return enabled, nil
|
||||
}
|
||||
|
||||
21
internal/configuration/sources/env/log.go
vendored
21
internal/configuration/sources/env/log.go
vendored
@@ -3,11 +3,10 @@ package env
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/golibs/logging"
|
||||
"github.com/qdm12/log"
|
||||
)
|
||||
|
||||
func readLog() (log settings.Log, err error) {
|
||||
@@ -19,13 +18,13 @@ func readLog() (log settings.Log, err error) {
|
||||
return log, nil
|
||||
}
|
||||
|
||||
func readLogLevel() (level *logging.Level, err error) {
|
||||
s := os.Getenv("LOG_LEVEL")
|
||||
func readLogLevel() (level *log.Level, err error) {
|
||||
s := getCleanedEnv("LOG_LEVEL")
|
||||
if s == "" {
|
||||
return nil, nil //nolint:nilnil
|
||||
}
|
||||
|
||||
level = new(logging.Level)
|
||||
level = new(log.Level)
|
||||
*level, err = parseLogLevel(s)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("environment variable LOG_LEVEL: %w", err)
|
||||
@@ -36,19 +35,19 @@ func readLogLevel() (level *logging.Level, err error) {
|
||||
|
||||
var ErrLogLevelUnknown = errors.New("log level is unknown")
|
||||
|
||||
func parseLogLevel(s string) (level logging.Level, err error) {
|
||||
func parseLogLevel(s string) (level log.Level, err error) {
|
||||
switch strings.ToLower(s) {
|
||||
case "debug":
|
||||
return logging.LevelDebug, nil
|
||||
return log.LevelDebug, nil
|
||||
case "info":
|
||||
return logging.LevelInfo, nil
|
||||
return log.LevelInfo, nil
|
||||
case "warning":
|
||||
return logging.LevelWarn, nil
|
||||
return log.LevelWarn, nil
|
||||
case "error":
|
||||
return logging.LevelError, nil
|
||||
return log.LevelError, nil
|
||||
default:
|
||||
return level, fmt.Errorf(
|
||||
"%w: %s: can be one of: debug, info, warning or error",
|
||||
"%w: %q is not valid and can be one of debug, info, warning or error",
|
||||
ErrLogLevelUnknown, s)
|
||||
}
|
||||
}
|
||||
|
||||
132
internal/configuration/sources/env/openvpn.go
vendored
132
internal/configuration/sources/env/openvpn.go
vendored
@@ -2,124 +2,126 @@ package env
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/govalid/binary"
|
||||
)
|
||||
|
||||
func (r *Reader) readOpenVPN() (
|
||||
func (s *Source) readOpenVPN() (
|
||||
openVPN settings.OpenVPN, err error) {
|
||||
defer func() {
|
||||
err = unsetEnvKeys([]string{"OPENVPN_CLIENTKEY", "OPENVPN_CLIENTCRT"}, err)
|
||||
err = unsetEnvKeys([]string{"OPENVPN_KEY", "OPENVPN_CERT",
|
||||
"OPENVPN_KEY_PASSPHRASE", "OPENVPN_ENCRYPTED_KEY"}, err)
|
||||
}()
|
||||
|
||||
openVPN.Version = os.Getenv("OPENVPN_VERSION")
|
||||
openVPN.User = r.readOpenVPNUser()
|
||||
openVPN.Password = r.readOpenVPNPassword()
|
||||
confFile := os.Getenv("OPENVPN_CUSTOM_CONFIG")
|
||||
openVPN.Version = getCleanedEnv("OPENVPN_VERSION")
|
||||
openVPN.User = s.readOpenVPNUser()
|
||||
openVPN.Password = s.readOpenVPNPassword()
|
||||
confFile := getCleanedEnv("OPENVPN_CUSTOM_CONFIG")
|
||||
if confFile != "" {
|
||||
openVPN.ConfFile = &confFile
|
||||
}
|
||||
|
||||
openVPN.Ciphers = envToCSV("OPENVPN_CIPHER")
|
||||
auth := os.Getenv("OPENVPN_AUTH")
|
||||
ciphersKey, _ := s.getEnvWithRetro("OPENVPN_CIPHERS", "OPENVPN_CIPHER")
|
||||
openVPN.Ciphers = envToCSV(ciphersKey)
|
||||
|
||||
auth := getCleanedEnv("OPENVPN_AUTH")
|
||||
if auth != "" {
|
||||
openVPN.Auth = &auth
|
||||
}
|
||||
|
||||
openVPN.ClientCrt, err = readBase64OrNil("OPENVPN_CLIENTCRT")
|
||||
if err != nil {
|
||||
return openVPN, fmt.Errorf("environment variable OPENVPN_CLIENTCRT: %w", err)
|
||||
}
|
||||
openVPN.Cert = envToStringPtr("OPENVPN_CERT")
|
||||
openVPN.Key = envToStringPtr("OPENVPN_KEY")
|
||||
openVPN.EncryptedKey = envToStringPtr("OPENVPN_ENCRYPTED_KEY")
|
||||
|
||||
openVPN.ClientKey, err = readBase64OrNil("OPENVPN_CLIENTKEY")
|
||||
if err != nil {
|
||||
return openVPN, fmt.Errorf("environment variable OPENVPN_CLIENTKEY: %w", err)
|
||||
}
|
||||
openVPN.KeyPassphrase = s.readOpenVPNKeyPassphrase()
|
||||
|
||||
openVPN.PIAEncPreset = r.readPIAEncryptionPreset()
|
||||
|
||||
openVPN.IPv6, err = envToBoolPtr("OPENVPN_IPV6")
|
||||
if err != nil {
|
||||
return openVPN, fmt.Errorf("environment variable OPENVPN_IPV6: %w", err)
|
||||
}
|
||||
openVPN.PIAEncPreset = s.readPIAEncryptionPreset()
|
||||
|
||||
openVPN.MSSFix, err = envToUint16Ptr("OPENVPN_MSSFIX")
|
||||
if err != nil {
|
||||
return openVPN, fmt.Errorf("environment variable OPENVPN_MSSFIX: %w", err)
|
||||
}
|
||||
|
||||
openVPN.Interface = os.Getenv("OPENVPN_INTERFACE")
|
||||
_, openVPN.Interface = s.getEnvWithRetro("VPN_INTERFACE", "OPENVPN_INTERFACE")
|
||||
|
||||
openVPN.Root, err = envToBoolPtr("OPENVPN_ROOT")
|
||||
openVPN.ProcessUser, err = s.readOpenVPNProcessUser()
|
||||
if err != nil {
|
||||
return openVPN, fmt.Errorf("environment variable OPENVPN_ROOT: %w", err)
|
||||
return openVPN, err
|
||||
}
|
||||
|
||||
// TODO ProcUser once Root is deprecated.
|
||||
|
||||
openVPN.Verbosity, err = envToIntPtr("OPENVPN_VERBOSITY")
|
||||
if err != nil {
|
||||
return openVPN, fmt.Errorf("environment variable OPENVPN_VERBOSITY: %w", err)
|
||||
}
|
||||
|
||||
flagsStr := getCleanedEnv("OPENVPN_FLAGS")
|
||||
if flagsStr != "" {
|
||||
openVPN.Flags = strings.Fields(flagsStr)
|
||||
}
|
||||
|
||||
return openVPN, nil
|
||||
}
|
||||
|
||||
func (r *Reader) readOpenVPNUser() (user string) {
|
||||
user = os.Getenv("OPENVPN_USER")
|
||||
if user == "" {
|
||||
// Retro-compatibility
|
||||
user = os.Getenv("USER")
|
||||
if user != "" {
|
||||
r.onRetroActive("USER", "OPENVPN_USER")
|
||||
}
|
||||
func (s *Source) readOpenVPNUser() (user *string) {
|
||||
user = new(string)
|
||||
_, *user = s.getEnvWithRetro("OPENVPN_USER", "USER")
|
||||
if *user == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove spaces in user ID to simplify user's life, thanks @JeordyR
|
||||
return strings.ReplaceAll(user, " ", "")
|
||||
*user = strings.ReplaceAll(*user, " ", "")
|
||||
return user
|
||||
}
|
||||
|
||||
func (r *Reader) readOpenVPNPassword() (password string) {
|
||||
password = os.Getenv("OPENVPN_PASSWORD")
|
||||
if password != "" {
|
||||
return password
|
||||
func (s *Source) readOpenVPNPassword() (password *string) {
|
||||
password = new(string)
|
||||
_, *password = s.getEnvWithRetro("OPENVPN_PASSWORD", "PASSWORD")
|
||||
if *password == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Retro-compatibility
|
||||
password = os.Getenv("PASSWORD")
|
||||
if password != "" {
|
||||
r.onRetroActive("PASSWORD", "OPENVPN_PASSWORD")
|
||||
}
|
||||
return password
|
||||
}
|
||||
|
||||
func readBase64OrNil(envKey string) (valueOrNil *string, err error) {
|
||||
value := os.Getenv(envKey)
|
||||
if value == "" {
|
||||
return nil, nil //nolint:nilnil
|
||||
func (s *Source) readOpenVPNKeyPassphrase() (passphrase *string) {
|
||||
passphrase = new(string)
|
||||
*passphrase = getCleanedEnv("OPENVPN_KEY_PASSPHRASE")
|
||||
if *passphrase == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
decoded, err := decodeBase64(value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &decoded, nil
|
||||
return passphrase
|
||||
}
|
||||
|
||||
func (r *Reader) readPIAEncryptionPreset() (presetPtr *string) {
|
||||
preset := strings.ToLower(os.Getenv("PIA_ENCRYPTION"))
|
||||
func (s *Source) readPIAEncryptionPreset() (presetPtr *string) {
|
||||
_, preset := s.getEnvWithRetro(
|
||||
"PRIVATE_INTERNET_ACCESS_OPENVPN_ENCRYPTION_PRESET",
|
||||
"PIA_ENCRYPTION", "ENCRYPTION")
|
||||
if preset != "" {
|
||||
return &preset
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Source) readOpenVPNProcessUser() (processUser string, err error) {
|
||||
key, value := s.getEnvWithRetro("OPENVPN_PROCESS_USER", "OPENVPN_ROOT")
|
||||
if key == "OPENVPN_PROCESS_USER" {
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// Retro-compatibility
|
||||
preset = strings.ToLower(os.Getenv("ENCRYPTION"))
|
||||
if preset != "" {
|
||||
r.onRetroActive("ENCRYPTION", "PIA_ENCRYPTION")
|
||||
return &preset
|
||||
if value == "" {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
return nil
|
||||
root, err := binary.Validate(value)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("environment variable %s: %w", key, err)
|
||||
}
|
||||
if root {
|
||||
return "root", nil
|
||||
}
|
||||
const defaultNonRootUser = "nonrootuser"
|
||||
return defaultNonRootUser, nil
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package env
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
@@ -11,43 +10,34 @@ import (
|
||||
"github.com/qdm12/govalid/port"
|
||||
)
|
||||
|
||||
func (r *Reader) readOpenVPNSelection() (
|
||||
func (s *Source) readOpenVPNSelection() (
|
||||
selection settings.OpenVPNSelection, err error) {
|
||||
confFile := os.Getenv("OPENVPN_CUSTOM_CONFIG")
|
||||
confFile := getCleanedEnv("OPENVPN_CUSTOM_CONFIG")
|
||||
if confFile != "" {
|
||||
selection.ConfFile = &confFile
|
||||
}
|
||||
|
||||
selection.TCP, err = r.readOpenVPNProtocol()
|
||||
selection.TCP, err = s.readOpenVPNProtocol()
|
||||
if err != nil {
|
||||
return selection, err
|
||||
}
|
||||
|
||||
selection.CustomPort, err = r.readOpenVPNCustomPort()
|
||||
selection.CustomPort, err = s.readOpenVPNCustomPort()
|
||||
if err != nil {
|
||||
return selection, err
|
||||
}
|
||||
|
||||
selection.PIAEncPreset = r.readPIAEncryptionPreset()
|
||||
selection.PIAEncPreset = s.readPIAEncryptionPreset()
|
||||
|
||||
return selection, nil
|
||||
}
|
||||
|
||||
var ErrOpenVPNProtocolNotValid = errors.New("OpenVPN protocol is not valid")
|
||||
|
||||
func (r *Reader) readOpenVPNProtocol() (tcp *bool, err error) {
|
||||
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")
|
||||
}
|
||||
}
|
||||
func (s *Source) readOpenVPNProtocol() (tcp *bool, err error) {
|
||||
envKey, protocol := s.getEnvWithRetro("OPENVPN_PROTOCOL", "PROTOCOL")
|
||||
|
||||
switch protocol {
|
||||
switch strings.ToLower(protocol) {
|
||||
case "":
|
||||
return nil, nil //nolint:nilnil
|
||||
case constants.UDP:
|
||||
@@ -60,21 +50,14 @@ func (r *Reader) readOpenVPNProtocol() (tcp *bool, err error) {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Reader) readOpenVPNCustomPort() (customPort *uint16, err error) {
|
||||
key := "OPENVPN_PORT"
|
||||
s := os.Getenv(key)
|
||||
if s == "" {
|
||||
// Retro-compatibility
|
||||
key = "PORT"
|
||||
s = os.Getenv(key)
|
||||
if s == "" {
|
||||
return nil, nil //nolint:nilnil
|
||||
}
|
||||
r.onRetroActive("PORT", "OPENVPN_PORT")
|
||||
func (s *Source) readOpenVPNCustomPort() (customPort *uint16, err error) {
|
||||
key, value := s.getEnvWithRetro("VPN_ENDPOINT_PORT", "PORT", "OPENVPN_PORT")
|
||||
if value == "" {
|
||||
return nil, nil //nolint:nilnil
|
||||
}
|
||||
|
||||
customPort = new(uint16)
|
||||
*customPort, err = port.Validate(s)
|
||||
*customPort, err = port.Validate(value)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("environment variable %s: %w", key, err)
|
||||
}
|
||||
|
||||
@@ -6,14 +6,22 @@ import (
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
)
|
||||
|
||||
func readPortForward() (
|
||||
func (s *Source) readPortForward() (
|
||||
portForwarding settings.PortForwarding, err error) {
|
||||
portForwarding.Enabled, err = envToBoolPtr("PORT_FORWARDING")
|
||||
key, _ := s.getEnvWithRetro(
|
||||
"PRIVATE_INTERNET_ACCESS_VPN_PORT_FORWARDING",
|
||||
"PORT_FORWARDING")
|
||||
portForwarding.Enabled, err = envToBoolPtr(key)
|
||||
if err != nil {
|
||||
return portForwarding, fmt.Errorf("environment variable PORT_FORWARDING: %w", err)
|
||||
return portForwarding, fmt.Errorf("environment variable %s: %w", key, err)
|
||||
}
|
||||
|
||||
portForwarding.Filepath = envToStringPtr("PORT_FORWARDING_STATUS_FILE")
|
||||
_, value := s.getEnvWithRetro(
|
||||
"PRIVATE_INTERNET_ACCESS_VPN_PORT_FORWARDING_STATUS_FILE",
|
||||
"PORT_FORWARDING_STATUS_FILE")
|
||||
if value != "" {
|
||||
portForwarding.Filepath = stringPtr(value)
|
||||
}
|
||||
|
||||
return portForwarding, nil
|
||||
}
|
||||
|
||||
28
internal/configuration/sources/env/pprof.go
vendored
Normal file
28
internal/configuration/sources/env/pprof.go
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
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
|
||||
}
|
||||
40
internal/configuration/sources/env/provider.go
vendored
40
internal/configuration/sources/env/provider.go
vendored
@@ -2,43 +2,47 @@ package env
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||
"github.com/qdm12/gluetun/internal/constants/vpn"
|
||||
)
|
||||
|
||||
func (r *Reader) readProvider(vpnType string) (provider settings.Provider, err error) {
|
||||
provider.Name = readVPNServiceProvider(vpnType)
|
||||
func (s *Source) readProvider(vpnType string) (provider settings.Provider, err error) {
|
||||
provider.Name = s.readVPNServiceProvider(vpnType)
|
||||
var providerName string
|
||||
if provider.Name != nil {
|
||||
providerName = *provider.Name
|
||||
}
|
||||
|
||||
provider.ServerSelection, err = r.readServerSelection(providerName, vpnType)
|
||||
provider.ServerSelection, err = s.readServerSelection(providerName, vpnType)
|
||||
if err != nil {
|
||||
return provider, fmt.Errorf("cannot read server selection settings: %w", err)
|
||||
return provider, fmt.Errorf("server selection: %w", err)
|
||||
}
|
||||
|
||||
provider.PortForwarding, err = readPortForward()
|
||||
provider.PortForwarding, err = s.readPortForward()
|
||||
if err != nil {
|
||||
return provider, fmt.Errorf("cannot read port forwarding settings: %w", err)
|
||||
return provider, fmt.Errorf("port forwarding: %w", err)
|
||||
}
|
||||
|
||||
return provider, nil
|
||||
}
|
||||
|
||||
func readVPNServiceProvider(vpnType string) (vpnProviderPtr *string) {
|
||||
s := strings.ToLower(os.Getenv("VPNSP"))
|
||||
switch {
|
||||
case vpnType != constants.Wireguard &&
|
||||
os.Getenv("OPENVPN_CUSTOM_CONFIG") != "": // retro compatibility
|
||||
return stringPtr(constants.Custom)
|
||||
case s == "":
|
||||
func (s *Source) readVPNServiceProvider(vpnType string) (vpnProviderPtr *string) {
|
||||
_, value := s.getEnvWithRetro("VPN_SERVICE_PROVIDER", "VPNSP")
|
||||
if value == "" {
|
||||
if vpnType != vpn.Wireguard && getCleanedEnv("OPENVPN_CUSTOM_CONFIG") != "" {
|
||||
// retro compatibility
|
||||
return stringPtr(providers.Custom)
|
||||
}
|
||||
return nil
|
||||
case s == "pia": // retro compatibility
|
||||
return stringPtr(constants.PrivateInternetAccess)
|
||||
}
|
||||
return stringPtr(s)
|
||||
|
||||
value = strings.ToLower(value)
|
||||
if value == "pia" { // retro compatibility
|
||||
return stringPtr(providers.PrivateInternetAccess)
|
||||
}
|
||||
|
||||
return stringPtr(value)
|
||||
}
|
||||
|
||||
23
internal/configuration/sources/env/publicip.go
vendored
23
internal/configuration/sources/env/publicip.go
vendored
@@ -2,25 +2,24 @@ package env
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
)
|
||||
|
||||
func (r *Reader) readPublicIP() (publicIP settings.PublicIP, err error) {
|
||||
func (s *Source) readPublicIP() (publicIP settings.PublicIP, err error) {
|
||||
publicIP.Period, err = readPublicIPPeriod()
|
||||
if err != nil {
|
||||
return publicIP, err
|
||||
}
|
||||
|
||||
publicIP.IPFilepath = r.readPublicIPFilepath()
|
||||
publicIP.IPFilepath = s.readPublicIPFilepath()
|
||||
|
||||
return publicIP, nil
|
||||
}
|
||||
|
||||
func readPublicIPPeriod() (period *time.Duration, err error) {
|
||||
s := os.Getenv("PUBLICIP_PERIOD")
|
||||
s := getCleanedEnv("PUBLICIP_PERIOD")
|
||||
if s == "" {
|
||||
return nil, nil //nolint:nilnil
|
||||
}
|
||||
@@ -34,18 +33,10 @@ func readPublicIPPeriod() (period *time.Duration, err error) {
|
||||
return period, nil
|
||||
}
|
||||
|
||||
func (r *Reader) readPublicIPFilepath() (filepath *string) {
|
||||
s := os.Getenv("PUBLICIP_FILE")
|
||||
if s != "" {
|
||||
return &s
|
||||
func (s *Source) readPublicIPFilepath() (filepath *string) {
|
||||
_, value := s.getEnvWithRetro("PUBLICIP_FILE", "IP_STATUS_FILE")
|
||||
if value != "" {
|
||||
return &value
|
||||
}
|
||||
|
||||
// Retro-compatibility
|
||||
s = os.Getenv("IP_STATUS_FILE")
|
||||
if s != "" {
|
||||
r.onRetroActive("IP_STATUS_FILE", "PUBLICIP_FILE")
|
||||
return &s
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
61
internal/configuration/sources/env/reader.go
vendored
61
internal/configuration/sources/env/reader.go
vendored
@@ -2,12 +2,9 @@ package env
|
||||
|
||||
import (
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gluetun/internal/configuration/sources"
|
||||
)
|
||||
|
||||
var _ sources.Source = (*Reader)(nil)
|
||||
|
||||
type Reader struct {
|
||||
type Source struct {
|
||||
warner Warner
|
||||
}
|
||||
|
||||
@@ -15,34 +12,36 @@ type Warner interface {
|
||||
Warn(s string)
|
||||
}
|
||||
|
||||
func New(warner Warner) *Reader {
|
||||
return &Reader{
|
||||
func New(warner Warner) *Source {
|
||||
return &Source{
|
||||
warner: warner,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Reader) Read() (settings settings.Settings, err error) {
|
||||
settings.VPN, err = r.readVPN()
|
||||
func (s *Source) String() string { return "environment variables" }
|
||||
|
||||
func (s *Source) Read() (settings settings.Settings, err error) {
|
||||
settings.VPN, err = s.readVPN()
|
||||
if err != nil {
|
||||
return settings, err
|
||||
}
|
||||
|
||||
settings.Firewall, err = r.readFirewall()
|
||||
settings.Firewall, err = s.readFirewall()
|
||||
if err != nil {
|
||||
return settings, err
|
||||
}
|
||||
|
||||
settings.System, err = r.readSystem()
|
||||
settings.System, err = s.readSystem()
|
||||
if err != nil {
|
||||
return settings, err
|
||||
}
|
||||
|
||||
settings.Health, err = r.ReadHealth()
|
||||
settings.Health, err = s.ReadHealth()
|
||||
if err != nil {
|
||||
return settings, err
|
||||
}
|
||||
|
||||
settings.HTTPProxy, err = r.readHTTPProxy()
|
||||
settings.HTTPProxy, err = s.readHTTPProxy()
|
||||
if err != nil {
|
||||
return settings, err
|
||||
}
|
||||
@@ -52,7 +51,7 @@ func (r *Reader) Read() (settings settings.Settings, err error) {
|
||||
return settings, err
|
||||
}
|
||||
|
||||
settings.PublicIP, err = r.readPublicIP()
|
||||
settings.PublicIP, err = s.readPublicIP()
|
||||
if err != nil {
|
||||
return settings, err
|
||||
}
|
||||
@@ -67,17 +66,22 @@ func (r *Reader) Read() (settings settings.Settings, err error) {
|
||||
return settings, err
|
||||
}
|
||||
|
||||
settings.Shadowsocks, err = r.readShadowsocks()
|
||||
settings.Shadowsocks, err = s.readShadowsocks()
|
||||
if err != nil {
|
||||
return settings, err
|
||||
}
|
||||
|
||||
settings.DNS, err = r.readDNS()
|
||||
settings.DNS, err = s.readDNS()
|
||||
if err != nil {
|
||||
return settings, err
|
||||
}
|
||||
|
||||
settings.ControlServer, err = readControlServer()
|
||||
settings.ControlServer, err = s.readControlServer()
|
||||
if err != nil {
|
||||
return settings, err
|
||||
}
|
||||
|
||||
settings.Pprof, err = readPprof()
|
||||
if err != nil {
|
||||
return settings, err
|
||||
}
|
||||
@@ -85,8 +89,29 @@ func (r *Reader) Read() (settings settings.Settings, err error) {
|
||||
return settings, nil
|
||||
}
|
||||
|
||||
func (r *Reader) onRetroActive(oldKey, newKey string) {
|
||||
r.warner.Warn(
|
||||
func (s *Source) onRetroActive(oldKey, newKey string) {
|
||||
s.warner.Warn(
|
||||
"You are using the old environment variable " + oldKey +
|
||||
", please consider changing it to " + newKey)
|
||||
}
|
||||
|
||||
// getEnvWithRetro returns the first environment variable
|
||||
// key and corresponding non empty value from the environment
|
||||
// variable keys given. It first goes through the retroKeys
|
||||
// and end on returning the value corresponding to the currentKey.
|
||||
// Note retroKeys should be in order from oldest to most
|
||||
// recent retro-compatibility key.
|
||||
func (s *Source) getEnvWithRetro(currentKey string,
|
||||
retroKeys ...string) (key, value string) {
|
||||
// We check retro-compatibility keys first since
|
||||
// the current key might be set in the Dockerfile.
|
||||
for _, key = range retroKeys {
|
||||
value = getCleanedEnv(key)
|
||||
if value != "" {
|
||||
s.onRetroActive(key, currentKey)
|
||||
return key, value
|
||||
}
|
||||
}
|
||||
|
||||
return currentKey, getCleanedEnv(currentKey)
|
||||
}
|
||||
|
||||
29
internal/configuration/sources/env/server.go
vendored
29
internal/configuration/sources/env/server.go
vendored
@@ -2,29 +2,24 @@ package env
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/govalid/binary"
|
||||
"github.com/qdm12/govalid/port"
|
||||
)
|
||||
|
||||
func readControlServer() (controlServer settings.ControlServer, err error) {
|
||||
func (s *Source) readControlServer() (controlServer settings.ControlServer, err error) {
|
||||
controlServer.Log, err = readControlServerLog()
|
||||
if err != nil {
|
||||
return controlServer, err
|
||||
}
|
||||
|
||||
controlServer.Port, err = readControlServerPort()
|
||||
if err != nil {
|
||||
return controlServer, err
|
||||
}
|
||||
controlServer.Address = s.readControlServerAddress()
|
||||
|
||||
return controlServer, nil
|
||||
}
|
||||
|
||||
func readControlServerLog() (enabled *bool, err error) {
|
||||
s := os.Getenv("HTTP_CONTROL_SERVER_LOG")
|
||||
s := getCleanedEnv("HTTP_CONTROL_SERVER_LOG")
|
||||
if s == "" {
|
||||
return nil, nil //nolint:nilnil
|
||||
}
|
||||
@@ -37,17 +32,17 @@ func readControlServerLog() (enabled *bool, err error) {
|
||||
return &log, nil
|
||||
}
|
||||
|
||||
func readControlServerPort() (p *uint16, err error) {
|
||||
s := os.Getenv("HTTP_CONTROL_SERVER_PORT")
|
||||
if s == "" {
|
||||
return nil, nil //nolint:nilnil
|
||||
func (s *Source) readControlServerAddress() (address *string) {
|
||||
key, value := s.getEnvWithRetro("HTTP_CONTROL_SERVER_ADDRESS", "HTTP_CONTROL_SERVER_PORT")
|
||||
if value == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
p = new(uint16)
|
||||
*p, err = port.Validate(s, port.OptionPortListening(os.Geteuid()))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("environment variable HTTP_CONTROL_SERVER_PORT: %w", err)
|
||||
if key == "HTTP_CONTROL_SERVER_ADDRESS" {
|
||||
return &value
|
||||
}
|
||||
|
||||
return p, nil
|
||||
address = new(string)
|
||||
*address = ":" + value
|
||||
return address
|
||||
}
|
||||
|
||||
@@ -4,48 +4,56 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrServerNumberNotValid = errors.New("server number is not valid")
|
||||
)
|
||||
|
||||
func (r *Reader) readServerSelection(vpnProvider, vpnType string) (
|
||||
func (s *Source) readServerSelection(vpnProvider, vpnType string) (
|
||||
ss settings.ServerSelection, err error) {
|
||||
ss.VPN = vpnType
|
||||
|
||||
ss.TargetIP, err = readOpenVPNTargetIP()
|
||||
ss.TargetIP, err = s.readOpenVPNTargetIP()
|
||||
if err != nil {
|
||||
return ss, err
|
||||
}
|
||||
|
||||
countriesCSV := os.Getenv("COUNTRY")
|
||||
if vpnProvider == constants.Cyberghost && countriesCSV == "" {
|
||||
// Retro-compatibility
|
||||
r.onRetroActive("REGION", "COUNTRY")
|
||||
countriesCSV = os.Getenv("REGION")
|
||||
}
|
||||
if countriesCSV != "" {
|
||||
ss.Countries = lowerAndSplit(countriesCSV)
|
||||
countriesKey, _ := s.getEnvWithRetro("SERVER_COUNTRIES", "COUNTRY")
|
||||
ss.Countries = envToCSV(countriesKey)
|
||||
if vpnProvider == providers.Cyberghost && len(ss.Countries) == 0 {
|
||||
// Retro-compatibility for Cyberghost using the REGION variable
|
||||
ss.Countries = envToCSV("REGION")
|
||||
if len(ss.Countries) > 0 {
|
||||
s.onRetroActive("REGION", "SERVER_COUNTRIES")
|
||||
}
|
||||
}
|
||||
|
||||
ss.Regions = envToCSV("REGION")
|
||||
ss.Cities = envToCSV("CITY")
|
||||
regionsKey, _ := s.getEnvWithRetro("SERVER_REGIONS", "REGION")
|
||||
ss.Regions = envToCSV(regionsKey)
|
||||
|
||||
citiesKey, _ := s.getEnvWithRetro("SERVER_CITIES", "CITY")
|
||||
ss.Cities = envToCSV(citiesKey)
|
||||
|
||||
ss.ISPs = envToCSV("ISP")
|
||||
ss.Hostnames = envToCSV("SERVER_HOSTNAME")
|
||||
ss.Names = envToCSV("SERVER_NAME")
|
||||
|
||||
if csv := os.Getenv("SERVER_NUMBER"); csv != "" {
|
||||
hostnamesKey, _ := s.getEnvWithRetro("SERVER_HOSTNAMES", "SERVER_HOSTNAME")
|
||||
ss.Hostnames = envToCSV(hostnamesKey)
|
||||
|
||||
serverNamesKey, _ := s.getEnvWithRetro("SERVER_NAMES", "SERVER_NAME")
|
||||
ss.Names = envToCSV(serverNamesKey)
|
||||
|
||||
if csv := getCleanedEnv("SERVER_NUMBER"); csv != "" {
|
||||
numbersStrings := strings.Split(csv, ",")
|
||||
numbers := make([]uint16, len(numbersStrings))
|
||||
for i, numberString := range numbersStrings {
|
||||
number, err := strconv.Atoi(numberString)
|
||||
const base, bitSize = 10, 16
|
||||
number, err := strconv.ParseInt(numberString, base, bitSize)
|
||||
if err != nil {
|
||||
return ss, fmt.Errorf("%w: %s",
|
||||
ErrServerNumberNotValid, numberString)
|
||||
@@ -59,9 +67,9 @@ func (r *Reader) readServerSelection(vpnProvider, vpnType string) (
|
||||
}
|
||||
|
||||
// Mullvad only
|
||||
ss.OwnedOnly, err = envToBoolPtr("OWNED")
|
||||
ss.OwnedOnly, err = s.readOwnedOnly()
|
||||
if err != nil {
|
||||
return ss, fmt.Errorf("environment variable OWNED: %w", err)
|
||||
return ss, err
|
||||
}
|
||||
|
||||
// VPNUnlimited and ProtonVPN only
|
||||
@@ -70,6 +78,12 @@ func (r *Reader) readServerSelection(vpnProvider, vpnType string) (
|
||||
return ss, fmt.Errorf("environment variable FREE_ONLY: %w", err)
|
||||
}
|
||||
|
||||
// VPNSecure only
|
||||
ss.PremiumOnly, err = envToBoolPtr("PREMIUM_ONLY")
|
||||
if err != nil {
|
||||
return ss, fmt.Errorf("environment variable PREMIUM_ONLY: %w", err)
|
||||
}
|
||||
|
||||
// VPNUnlimited only
|
||||
ss.MultiHopOnly, err = envToBoolPtr("MULTIHOP_ONLY")
|
||||
if err != nil {
|
||||
@@ -82,12 +96,12 @@ func (r *Reader) readServerSelection(vpnProvider, vpnType string) (
|
||||
return ss, fmt.Errorf("environment variable STREAM_ONLY: %w", err)
|
||||
}
|
||||
|
||||
ss.OpenVPN, err = r.readOpenVPNSelection()
|
||||
ss.OpenVPN, err = s.readOpenVPNSelection()
|
||||
if err != nil {
|
||||
return ss, err
|
||||
}
|
||||
|
||||
ss.Wireguard, err = r.readWireguardSelection()
|
||||
ss.Wireguard, err = s.readWireguardSelection()
|
||||
if err != nil {
|
||||
return ss, err
|
||||
}
|
||||
@@ -99,17 +113,26 @@ var (
|
||||
ErrInvalidIP = errors.New("invalid IP address")
|
||||
)
|
||||
|
||||
func readOpenVPNTargetIP() (ip net.IP, err error) {
|
||||
s := os.Getenv("OPENVPN_TARGET_IP")
|
||||
if s == "" {
|
||||
func (s *Source) readOpenVPNTargetIP() (ip net.IP, err error) {
|
||||
envKey, value := s.getEnvWithRetro("VPN_ENDPOINT_IP", "OPENVPN_TARGET_IP")
|
||||
if value == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
ip = net.ParseIP(s)
|
||||
ip = net.ParseIP(value)
|
||||
if ip == nil {
|
||||
return nil, fmt.Errorf("environment variable OPENVPN_TARGET_IP: %w: %s",
|
||||
ErrInvalidIP, s)
|
||||
return nil, fmt.Errorf("environment variable %s: %w: %s",
|
||||
envKey, ErrInvalidIP, value)
|
||||
}
|
||||
|
||||
return ip, nil
|
||||
}
|
||||
|
||||
func (s *Source) readOwnedOnly() (ownedOnly *bool, err error) {
|
||||
envKey, _ := s.getEnvWithRetro("OWNED_ONLY", "OWNED")
|
||||
ownedOnly, err = envToBoolPtr(envKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("environment variable %s: %w", envKey, err)
|
||||
}
|
||||
return ownedOnly, nil
|
||||
}
|
||||
|
||||
@@ -2,53 +2,43 @@ package env
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
)
|
||||
|
||||
func (r *Reader) readShadowsocks() (shadowsocks settings.Shadowsocks, err error) {
|
||||
func (s *Source) readShadowsocks() (shadowsocks settings.Shadowsocks, err error) {
|
||||
shadowsocks.Enabled, err = envToBoolPtr("SHADOWSOCKS")
|
||||
if err != nil {
|
||||
return shadowsocks, fmt.Errorf("environment variable SHADOWSOCKS: %w", err)
|
||||
}
|
||||
|
||||
shadowsocks.Address = r.readShadowsocksAddress()
|
||||
shadowsocks.Address = s.readShadowsocksAddress()
|
||||
shadowsocks.LogAddresses, err = envToBoolPtr("SHADOWSOCKS_LOG")
|
||||
if err != nil {
|
||||
return shadowsocks, fmt.Errorf("environment variable SHADOWSOCKS_LOG: %w", err)
|
||||
}
|
||||
shadowsocks.CipherName = r.readShadowsocksCipher()
|
||||
shadowsocks.CipherName = s.readShadowsocksCipher()
|
||||
shadowsocks.Password = envToStringPtr("SHADOWSOCKS_PASSWORD")
|
||||
|
||||
return shadowsocks, nil
|
||||
}
|
||||
|
||||
func (r *Reader) readShadowsocksAddress() (address string) {
|
||||
address = os.Getenv("SHADOWSOCKS_LISTENING_ADDRESS")
|
||||
if address != "" {
|
||||
return address
|
||||
func (s *Source) readShadowsocksAddress() (address string) {
|
||||
key, value := s.getEnvWithRetro("SHADOWSOCKS_LISTENING_ADDRESS", "SHADOWSOCKS_PORT")
|
||||
if value == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
if key == "SHADOWSOCKS_LISTENING_ADDRESS" {
|
||||
return value
|
||||
}
|
||||
|
||||
// Retro-compatibility
|
||||
portString := os.Getenv("SHADOWSOCKS_PORT")
|
||||
if portString != "" {
|
||||
r.onRetroActive("SHADOWSOCKS_PORT", "SHADOWSOCKS_LISTENING_ADDRESS")
|
||||
return ":" + portString
|
||||
}
|
||||
|
||||
return ""
|
||||
return ":" + value
|
||||
}
|
||||
|
||||
func (r *Reader) readShadowsocksCipher() (cipher string) {
|
||||
cipher = os.Getenv("SHADOWSOCKS_CIPHER")
|
||||
if cipher != "" {
|
||||
return cipher
|
||||
}
|
||||
// Retro-compatibility
|
||||
cipher = os.Getenv("SHADOWSOCKS_METHOD")
|
||||
if cipher != "" {
|
||||
r.onRetroActive("SHADOWSOCKS_METHOD", "SHADOWSOCKS_CIPHER")
|
||||
}
|
||||
return cipher
|
||||
func (s *Source) readShadowsocksCipher() (cipher string) {
|
||||
_, cipher = s.getEnvWithRetro("SHADOWSOCKS_CIPHER", "SHADOWSOCKS_METHOD")
|
||||
return strings.ToLower(cipher)
|
||||
}
|
||||
|
||||
42
internal/configuration/sources/env/system.go
vendored
42
internal/configuration/sources/env/system.go
vendored
@@ -3,7 +3,6 @@ package env
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
@@ -15,49 +14,42 @@ var (
|
||||
ErrSystemTimezoneNotValid = errors.New("timezone is not valid")
|
||||
)
|
||||
|
||||
func (r *Reader) readSystem() (system settings.System, err error) {
|
||||
system.PUID, err = r.readID("PUID", "UID")
|
||||
func (s *Source) readSystem() (system settings.System, err error) {
|
||||
system.PUID, err = s.readID("PUID", "UID")
|
||||
if err != nil {
|
||||
return system, err
|
||||
}
|
||||
|
||||
system.PGID, err = r.readID("PGID", "GID")
|
||||
system.PGID, err = s.readID("PGID", "GID")
|
||||
if err != nil {
|
||||
return system, err
|
||||
}
|
||||
|
||||
system.Timezone = os.Getenv("TZ")
|
||||
system.Timezone = getCleanedEnv("TZ")
|
||||
|
||||
return system, nil
|
||||
}
|
||||
|
||||
var ErrSystemIDNotValid = errors.New("system ID is not valid")
|
||||
|
||||
func (r *Reader) readID(key, retroKey string) (
|
||||
id *uint16, err error) {
|
||||
idEnvKey := key
|
||||
idString := os.Getenv(key)
|
||||
if idString == "" {
|
||||
// retro-compatibility
|
||||
idString = os.Getenv(retroKey)
|
||||
if idString != "" {
|
||||
idEnvKey = retroKey
|
||||
r.onRetroActive(retroKey, key)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Source) readID(key, retroKey string) (
|
||||
id *uint32, err error) {
|
||||
idEnvKey, idString := s.getEnvWithRetro(key, retroKey)
|
||||
if idString == "" {
|
||||
return nil, nil //nolint:nilnil
|
||||
}
|
||||
|
||||
idInt, err := strconv.Atoi(idString)
|
||||
const base = 10
|
||||
const bitSize = 64
|
||||
const max = uint64(^uint32(0))
|
||||
idUint64, err := strconv.ParseUint(idString, base, bitSize)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("environment variable %s: %w: %s: %s",
|
||||
idEnvKey, ErrSystemIDNotValid, idString, err)
|
||||
} else if idInt < 0 || idInt > 65535 {
|
||||
return nil, fmt.Errorf("environment variable %s: %w: %d: must be between 0 and 65535",
|
||||
idEnvKey, ErrSystemIDNotValid, idInt)
|
||||
return nil, fmt.Errorf("environment variable %s: %w: %s",
|
||||
idEnvKey, ErrSystemIDNotValid, err)
|
||||
} else if idUint64 > max {
|
||||
return nil, fmt.Errorf("environment variable %s: %w: %d: must be between 0 and %d",
|
||||
idEnvKey, ErrSystemIDNotValid, idUint64, max)
|
||||
}
|
||||
|
||||
return uint16Ptr(uint16(idInt)), nil
|
||||
return uint32Ptr(uint32(idUint64)), nil
|
||||
}
|
||||
|
||||
@@ -14,15 +14,51 @@ func Test_Reader_readID(t *testing.T) {
|
||||
keyValue string
|
||||
retroKeyPrefix string
|
||||
retroValue string
|
||||
id *uint16
|
||||
id *uint32
|
||||
errWrapped error
|
||||
errMessage string
|
||||
}{
|
||||
"empty string": {
|
||||
keyPrefix: "ID",
|
||||
retroKeyPrefix: "RETRO_ID",
|
||||
},
|
||||
"invalid string": {
|
||||
keyPrefix: "ID",
|
||||
keyValue: "invalid",
|
||||
retroKeyPrefix: "RETRO_ID",
|
||||
errWrapped: ErrSystemIDNotValid,
|
||||
errMessage: `environment variable IDTest_Reader_readID/invalid_string: ` +
|
||||
`system ID is not valid: ` +
|
||||
`strconv.ParseUint: parsing "invalid": invalid syntax`,
|
||||
},
|
||||
"negative number": {
|
||||
keyPrefix: "ID",
|
||||
keyValue: "-1",
|
||||
retroKeyPrefix: "RETRO_ID",
|
||||
errWrapped: ErrSystemIDNotValid,
|
||||
errMessage: `environment variable IDTest_Reader_readID/negative_number: ` +
|
||||
`system ID is not valid: ` +
|
||||
`strconv.ParseUint: parsing "-1": invalid syntax`,
|
||||
},
|
||||
"id 1000": {
|
||||
keyPrefix: "ID",
|
||||
keyValue: "1000",
|
||||
retroKeyPrefix: "RETRO_ID",
|
||||
id: uint16Ptr(1000),
|
||||
id: uint32Ptr(1000),
|
||||
},
|
||||
"max id": {
|
||||
keyPrefix: "ID",
|
||||
keyValue: "4294967295",
|
||||
retroKeyPrefix: "RETRO_ID",
|
||||
id: uint32Ptr(4294967295),
|
||||
},
|
||||
"above max id": {
|
||||
keyPrefix: "ID",
|
||||
keyValue: "4294967296",
|
||||
retroKeyPrefix: "RETRO_ID",
|
||||
errWrapped: ErrSystemIDNotValid,
|
||||
errMessage: `environment variable IDTest_Reader_readID/above_max_id: ` +
|
||||
`system ID is not valid: 4294967296: must be between 0 and 4294967295`,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -38,8 +74,8 @@ func Test_Reader_readID(t *testing.T) {
|
||||
setTestEnv(t, key, testCase.keyValue)
|
||||
setTestEnv(t, retroKey, testCase.retroValue)
|
||||
|
||||
reader := &Reader{}
|
||||
id, err := reader.readID(key, retroKey)
|
||||
source := &Source{}
|
||||
id, err := source.readID(key, retroKey)
|
||||
|
||||
assert.ErrorIs(t, err, testCase.errWrapped)
|
||||
if err != nil {
|
||||
|
||||
20
internal/configuration/sources/env/updater.go
vendored
20
internal/configuration/sources/env/updater.go
vendored
@@ -2,12 +2,9 @@ package env
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
)
|
||||
|
||||
func readUpdater() (updater settings.Updater, err error) {
|
||||
@@ -21,19 +18,18 @@ func readUpdater() (updater settings.Updater, err error) {
|
||||
return updater, err
|
||||
}
|
||||
|
||||
// TODO use current provider being used
|
||||
for _, provider := range constants.AllProviders() {
|
||||
if provider == constants.Custom {
|
||||
continue
|
||||
}
|
||||
updater.Providers = append(updater.Providers, provider)
|
||||
updater.MinRatio, err = envToFloat64("UPDATER_MIN_RATIO")
|
||||
if err != nil {
|
||||
return updater, fmt.Errorf("environment variable UPDATER_MIN_RATIO: %w", err)
|
||||
}
|
||||
|
||||
updater.Providers = envToCSV("UPDATER_VPN_SERVICE_PROVIDERS")
|
||||
|
||||
return updater, nil
|
||||
}
|
||||
|
||||
func readUpdaterPeriod() (period *time.Duration, err error) {
|
||||
s := os.Getenv("UPDATER_PERIOD")
|
||||
s := getCleanedEnv("UPDATER_PERIOD")
|
||||
if s == "" {
|
||||
return nil, nil //nolint:nilnil
|
||||
}
|
||||
@@ -45,11 +41,11 @@ func readUpdaterPeriod() (period *time.Duration, err error) {
|
||||
return period, nil
|
||||
}
|
||||
|
||||
func readUpdaterDNSAddress() (ip net.IP, err error) {
|
||||
func readUpdaterDNSAddress() (address string, err error) {
|
||||
// TODO this is currently using Cloudflare in
|
||||
// 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.
|
||||
// use custom future encrypted DNS written in Go without blocking
|
||||
// as it's too much trouble to start another parallel unbound instance for now.
|
||||
return nil, nil
|
||||
return "", nil
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package env
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/govalid/binary"
|
||||
@@ -18,7 +17,7 @@ func readVersion() (version settings.Version, err error) {
|
||||
}
|
||||
|
||||
func readVersionEnabled() (enabled *bool, err error) {
|
||||
s := os.Getenv("VERSION_INFORMATION")
|
||||
s := getCleanedEnv("VERSION_INFORMATION")
|
||||
if s == "" {
|
||||
return nil, nil //nolint:nilnil
|
||||
}
|
||||
|
||||
17
internal/configuration/sources/env/vpn.go
vendored
17
internal/configuration/sources/env/vpn.go
vendored
@@ -2,28 +2,27 @@ package env
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
)
|
||||
|
||||
func (r *Reader) readVPN() (vpn settings.VPN, err error) {
|
||||
vpn.Type = strings.ToLower(os.Getenv("VPN_TYPE"))
|
||||
func (s *Source) readVPN() (vpn settings.VPN, err error) {
|
||||
vpn.Type = strings.ToLower(getCleanedEnv("VPN_TYPE"))
|
||||
|
||||
vpn.Provider, err = r.readProvider(vpn.Type)
|
||||
vpn.Provider, err = s.readProvider(vpn.Type)
|
||||
if err != nil {
|
||||
return vpn, fmt.Errorf("cannot read provider settings: %w", err)
|
||||
return vpn, fmt.Errorf("VPN provider: %w", err)
|
||||
}
|
||||
|
||||
vpn.OpenVPN, err = r.readOpenVPN()
|
||||
vpn.OpenVPN, err = s.readOpenVPN()
|
||||
if err != nil {
|
||||
return vpn, fmt.Errorf("cannot read OpenVPN settings: %w", err)
|
||||
return vpn, fmt.Errorf("OpenVPN: %w", err)
|
||||
}
|
||||
|
||||
vpn.Wireguard, err = readWireguard()
|
||||
vpn.Wireguard, err = s.readWireguard()
|
||||
if err != nil {
|
||||
return vpn, fmt.Errorf("cannot read Wireguard settings: %w", err)
|
||||
return vpn, fmt.Errorf("wireguard: %w", err)
|
||||
}
|
||||
|
||||
return vpn, nil
|
||||
|
||||
14
internal/configuration/sources/env/wireguard.go
vendored
14
internal/configuration/sources/env/wireguard.go
vendored
@@ -9,22 +9,23 @@ import (
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
)
|
||||
|
||||
func readWireguard() (wireguard settings.Wireguard, err error) {
|
||||
func (s *Source) readWireguard() (wireguard settings.Wireguard, err error) {
|
||||
defer func() {
|
||||
err = unsetEnvKeys([]string{"WIREGUARD_PRIVATE_KEY", "WIREGUARD_PRESHARED_KEY"}, err)
|
||||
}()
|
||||
wireguard.PrivateKey = envToStringPtr("WIREGUARD_PRIVATE_KEY")
|
||||
wireguard.PreSharedKey = envToStringPtr("WIREGUARD_PRESHARED_KEY")
|
||||
wireguard.Interface = os.Getenv("WIREGUARD_INTERFACE")
|
||||
wireguard.Addresses, err = readWireguardAddresses()
|
||||
_, wireguard.Interface = s.getEnvWithRetro("VPN_INTERFACE", "WIREGUARD_INTERFACE")
|
||||
wireguard.Implementation = os.Getenv("WIREGUARD_IMPLEMENTATION")
|
||||
wireguard.Addresses, err = s.readWireguardAddresses()
|
||||
if err != nil {
|
||||
return wireguard, err // already wrapped
|
||||
}
|
||||
return wireguard, nil
|
||||
}
|
||||
|
||||
func readWireguardAddresses() (addresses []net.IPNet, err error) {
|
||||
addressesCSV := os.Getenv("WIREGUARD_ADDRESS")
|
||||
func (s *Source) readWireguardAddresses() (addresses []net.IPNet, err error) {
|
||||
key, addressesCSV := s.getEnvWithRetro("WIREGUARD_ADDRESSES", "WIREGUARD_ADDRESS")
|
||||
if addressesCSV == "" {
|
||||
return nil, nil
|
||||
}
|
||||
@@ -32,9 +33,10 @@ func readWireguardAddresses() (addresses []net.IPNet, err error) {
|
||||
addressStrings := strings.Split(addressesCSV, ",")
|
||||
addresses = make([]net.IPNet, len(addressStrings))
|
||||
for i, addressString := range addressStrings {
|
||||
addressString = strings.TrimSpace(addressString)
|
||||
ip, ipNet, err := net.ParseCIDR(addressString)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("environment variable WIREGUARD_ADDRESS: %w", err)
|
||||
return nil, fmt.Errorf("environment variable %s: %w", key, err)
|
||||
}
|
||||
ipNet.IP = ip
|
||||
addresses[i] = *ipNet
|
||||
|
||||
@@ -4,59 +4,53 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/govalid/port"
|
||||
)
|
||||
|
||||
func (r *Reader) readWireguardSelection() (
|
||||
func (s *Source) readWireguardSelection() (
|
||||
selection settings.WireguardSelection, err error) {
|
||||
selection.EndpointIP, err = readWireguardEndpointIP()
|
||||
selection.EndpointIP, err = s.readWireguardEndpointIP()
|
||||
if err != nil {
|
||||
return selection, err
|
||||
}
|
||||
|
||||
selection.EndpointPort, err = r.readWireguardCustomPort()
|
||||
selection.EndpointPort, err = s.readWireguardCustomPort()
|
||||
if err != nil {
|
||||
return selection, err
|
||||
}
|
||||
|
||||
selection.PublicKey = os.Getenv("WIREGUARD_PUBLIC_KEY")
|
||||
selection.PublicKey = getCleanedEnv("WIREGUARD_PUBLIC_KEY")
|
||||
|
||||
return selection, nil
|
||||
}
|
||||
|
||||
var ErrIPAddressParse = errors.New("cannot parse IP address")
|
||||
|
||||
func readWireguardEndpointIP() (endpointIP net.IP, err error) {
|
||||
s := os.Getenv("WIREGUARD_ENDPOINT_IP")
|
||||
if s == "" {
|
||||
func (s *Source) readWireguardEndpointIP() (endpointIP net.IP, err error) {
|
||||
key, value := s.getEnvWithRetro("VPN_ENDPOINT_IP", "WIREGUARD_ENDPOINT_IP")
|
||||
if value == "" {
|
||||
return nil, nil
|
||||
}
|
||||
endpointIP = net.ParseIP(s)
|
||||
|
||||
endpointIP = net.ParseIP(value)
|
||||
if endpointIP == nil {
|
||||
return nil, fmt.Errorf("environment variable WIREGUARD_ENDPOINT_IP: %w: %s",
|
||||
ErrIPAddressParse, s)
|
||||
return nil, fmt.Errorf("environment variable %s: %w: %s",
|
||||
key, ErrIPAddressParse, value)
|
||||
}
|
||||
|
||||
return endpointIP, nil
|
||||
}
|
||||
|
||||
func (r *Reader) readWireguardCustomPort() (customPort *uint16, err error) {
|
||||
key := "WIREGUARD_ENDPOINT_PORT"
|
||||
s := os.Getenv(key)
|
||||
if s == "" {
|
||||
// Retro-compatibility
|
||||
key = "WIREGUARD_PORT"
|
||||
s = os.Getenv(key)
|
||||
if s == "" {
|
||||
return nil, nil //nolint:nilnil
|
||||
}
|
||||
r.onRetroActive("WIREGUARD_PORT", "WIREGUARD_ENDPOINT_PORT")
|
||||
func (s *Source) readWireguardCustomPort() (customPort *uint16, err error) {
|
||||
key, value := s.getEnvWithRetro("VPN_ENDPOINT_PORT", "WIREGUARD_ENDPOINT_PORT")
|
||||
if value == "" {
|
||||
return nil, nil //nolint:nilnil
|
||||
}
|
||||
|
||||
customPort = new(uint16)
|
||||
*customPort, err = port.Validate(s)
|
||||
*customPort, err = port.Validate(value)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("environment variable %s: %w", key, err)
|
||||
}
|
||||
|
||||
@@ -2,4 +2,4 @@ package files
|
||||
|
||||
import "github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
|
||||
func (r *Reader) ReadHealth() (settings settings.Health, err error) { return settings, nil }
|
||||
func (s *Source) ReadHealth() (settings settings.Health, err error) { return settings, nil }
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package files
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/openvpn/extract"
|
||||
)
|
||||
|
||||
// ReadFromFile reads the content of the file as a string.
|
||||
@@ -32,3 +35,21 @@ func ReadFromFile(filepath string) (s *string, err error) {
|
||||
content = strings.TrimSuffix(content, "\n")
|
||||
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,18 +4,29 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
)
|
||||
|
||||
func (r *Reader) readOpenVPN() (settings settings.OpenVPN, err error) {
|
||||
settings.ClientKey, err = ReadFromFile(constants.ClientKey)
|
||||
const (
|
||||
// OpenVPNClientKeyPath is the OpenVPN client key filepath.
|
||||
OpenVPNClientKeyPath = "/gluetun/client.key"
|
||||
// OpenVPNClientCertificatePath is the OpenVPN client certificate filepath.
|
||||
OpenVPNClientCertificatePath = "/gluetun/client.crt"
|
||||
openVPNEncryptedKey = "/gluetun/openvpn_encrypted_key"
|
||||
)
|
||||
|
||||
func (s *Source) readOpenVPN() (settings settings.OpenVPN, err error) {
|
||||
settings.Key, err = readPEMFile(OpenVPNClientKeyPath)
|
||||
if err != nil {
|
||||
return settings, fmt.Errorf("cannot read client key: %w", err)
|
||||
return settings, fmt.Errorf("client key: %w", err)
|
||||
}
|
||||
|
||||
settings.ClientCrt, err = ReadFromFile(constants.ClientCertificate)
|
||||
settings.Cert, err = readPEMFile(OpenVPNClientCertificatePath)
|
||||
if err != nil {
|
||||
return settings, fmt.Errorf("cannot read client certificate: %w", err)
|
||||
return settings, fmt.Errorf("client certificate: %w", err)
|
||||
}
|
||||
settings.EncryptedKey, err = readPEMFile(openVPNEncryptedKey)
|
||||
if err != nil {
|
||||
return settings, fmt.Errorf("reading encrypted key file: %w", err)
|
||||
}
|
||||
|
||||
return settings, nil
|
||||
|
||||
@@ -2,24 +2,23 @@ package files
|
||||
|
||||
import (
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gluetun/internal/configuration/sources"
|
||||
)
|
||||
|
||||
var _ sources.Source = (*Reader)(nil)
|
||||
type Source struct{}
|
||||
|
||||
type Reader struct{}
|
||||
|
||||
func New() *Reader {
|
||||
return &Reader{}
|
||||
func New() *Source {
|
||||
return &Source{}
|
||||
}
|
||||
|
||||
func (r *Reader) Read() (settings settings.Settings, err error) {
|
||||
settings.VPN, err = r.readVPN()
|
||||
func (s *Source) String() string { return "files" }
|
||||
|
||||
func (s *Source) Read() (settings settings.Settings, err error) {
|
||||
settings.VPN, err = s.readVPN()
|
||||
if err != nil {
|
||||
return settings, err
|
||||
}
|
||||
|
||||
settings.System, err = r.readSystem()
|
||||
settings.System, err = s.readSystem()
|
||||
if err != nil {
|
||||
return settings, err
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
)
|
||||
|
||||
func (r *Reader) readSystem() (system settings.System, err error) {
|
||||
func (s *Source) readSystem() (system settings.System, err error) {
|
||||
// TODO timezone from /etc/localtime
|
||||
return system, nil
|
||||
}
|
||||
|
||||
@@ -6,10 +6,10 @@ import (
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
)
|
||||
|
||||
func (r *Reader) readVPN() (vpn settings.VPN, err error) {
|
||||
vpn.OpenVPN, err = r.readOpenVPN()
|
||||
func (s *Source) readVPN() (vpn settings.VPN, err error) {
|
||||
vpn.OpenVPN, err = s.readOpenVPN()
|
||||
if err != nil {
|
||||
return vpn, fmt.Errorf("cannot read OpenVPN settings: %w", err)
|
||||
return vpn, fmt.Errorf("OpenVPN: %w", err)
|
||||
}
|
||||
|
||||
return vpn, nil
|
||||
|
||||
@@ -1,32 +1,44 @@
|
||||
package mux
|
||||
package merge
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gluetun/internal/configuration/sources"
|
||||
)
|
||||
|
||||
var _ sources.Source = (*Reader)(nil)
|
||||
|
||||
type Reader struct {
|
||||
sources []sources.Source
|
||||
type ConfigSource interface {
|
||||
Read() (settings settings.Settings, err error)
|
||||
ReadHealth() (settings settings.Health, err error)
|
||||
String() string
|
||||
}
|
||||
|
||||
func New(sources ...sources.Source) *Reader {
|
||||
return &Reader{
|
||||
type Source struct {
|
||||
sources []ConfigSource
|
||||
}
|
||||
|
||||
func New(sources ...ConfigSource) *Source {
|
||||
return &Source{
|
||||
sources: sources,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Source) String() string {
|
||||
sources := make([]string, len(s.sources))
|
||||
for i := range s.sources {
|
||||
sources[i] = s.sources[i].String()
|
||||
}
|
||||
return strings.Join(sources, ", ")
|
||||
}
|
||||
|
||||
// Read reads the settings for each source, merging unset fields
|
||||
// with field set by the next source.
|
||||
// It then set defaults to remaining unset fields.
|
||||
func (r *Reader) Read() (settings settings.Settings, err error) {
|
||||
for _, source := range r.sources {
|
||||
func (s *Source) Read() (settings settings.Settings, err error) {
|
||||
for _, source := range s.sources {
|
||||
settingsFromSource, err := source.Read()
|
||||
if err != nil {
|
||||
return settings, fmt.Errorf("cannot read from source %T: %w", source, err)
|
||||
return settings, fmt.Errorf("reading from %s: %w", source, err)
|
||||
}
|
||||
settings.MergeWith(settingsFromSource)
|
||||
}
|
||||
@@ -38,11 +50,11 @@ func (r *Reader) Read() (settings settings.Settings, err error) {
|
||||
// with field set by the next source.
|
||||
// It then set defaults to remaining unset fields, and validate
|
||||
// all the fields.
|
||||
func (r *Reader) ReadHealth() (settings settings.Health, err error) {
|
||||
for _, source := range r.sources {
|
||||
func (s *Source) ReadHealth() (settings settings.Health, err error) {
|
||||
for _, source := range s.sources {
|
||||
settingsFromSource, err := source.ReadHealth()
|
||||
if err != nil {
|
||||
return settings, fmt.Errorf("cannot read from source %T: %w", source, err)
|
||||
return settings, fmt.Errorf("reading from %s: %w", source, err)
|
||||
}
|
||||
settings.MergeWith(settingsFromSource)
|
||||
}
|
||||
@@ -2,4 +2,4 @@ package secrets
|
||||
|
||||
import "github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
|
||||
func (r *Reader) ReadHealth() (settings settings.Health, err error) { return settings, nil }
|
||||
func (s *Source) ReadHealth() (settings settings.Health, err error) { return settings, nil }
|
||||
|
||||
@@ -1,31 +1,48 @@
|
||||
package secrets
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"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) (
|
||||
stringPtr *string, err error) {
|
||||
path := os.Getenv(secretPathEnvKey)
|
||||
path := getCleanedEnv(secretPathEnvKey)
|
||||
if path == "" {
|
||||
path = defaultSecretPath
|
||||
}
|
||||
return files.ReadFromFile(path)
|
||||
}
|
||||
|
||||
func readSecretFileAsString(secretPathEnvKey, defaultSecretPath string) (
|
||||
s string, err error) {
|
||||
path := os.Getenv(secretPathEnvKey)
|
||||
if path == "" {
|
||||
path = defaultSecretPath
|
||||
}
|
||||
stringPtr, err := files.ReadFromFile(path)
|
||||
func readPEMSecretFile(secretPathEnvKey, defaultSecretPath string) (
|
||||
base64Ptr *string, err error) {
|
||||
pemData, err := readSecretFileAsStringPtr(secretPathEnvKey, defaultSecretPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
} else if stringPtr == nil {
|
||||
return "", nil
|
||||
return nil, fmt.Errorf("reading secret file: %w", err)
|
||||
}
|
||||
return *stringPtr, nil
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ func readHTTPProxy() (settings settings.HTTPProxy, err error) {
|
||||
"/run/secrets/httpproxy_user",
|
||||
)
|
||||
if err != nil {
|
||||
return settings, fmt.Errorf("cannot read HTTP proxy user secret file: %w", err)
|
||||
return settings, fmt.Errorf("reading HTTP proxy user secret file: %w", err)
|
||||
}
|
||||
|
||||
settings.Password, err = readSecretFileAsStringPtr(
|
||||
@@ -20,7 +20,7 @@ func readHTTPProxy() (settings settings.HTTPProxy, err error) {
|
||||
"/run/secrets/httpproxy_password",
|
||||
)
|
||||
if err != nil {
|
||||
return settings, fmt.Errorf("cannot read OpenVPN password secret file: %w", err)
|
||||
return settings, fmt.Errorf("reading OpenVPN password secret file: %w", err)
|
||||
}
|
||||
|
||||
return settings, nil
|
||||
|
||||
@@ -8,36 +8,52 @@ import (
|
||||
|
||||
func readOpenVPN() (
|
||||
settings settings.OpenVPN, err error) {
|
||||
settings.User, err = readSecretFileAsString(
|
||||
settings.User, err = readSecretFileAsStringPtr(
|
||||
"OPENVPN_USER_SECRETFILE",
|
||||
"/run/secrets/openvpn_user",
|
||||
)
|
||||
if err != nil {
|
||||
return settings, fmt.Errorf("cannot read user file: %w", err)
|
||||
return settings, fmt.Errorf("reading user file: %w", err)
|
||||
}
|
||||
|
||||
settings.Password, err = readSecretFileAsString(
|
||||
settings.Password, err = readSecretFileAsStringPtr(
|
||||
"OPENVPN_PASSWORD_SECRETFILE",
|
||||
"/run/secrets/openvpn_password",
|
||||
)
|
||||
if err != nil {
|
||||
return settings, fmt.Errorf("cannot read password file: %w", err)
|
||||
return settings, fmt.Errorf("reading password file: %w", err)
|
||||
}
|
||||
|
||||
settings.ClientKey, err = readSecretFileAsStringPtr(
|
||||
settings.Key, err = readPEMSecretFile(
|
||||
"OPENVPN_CLIENTKEY_SECRETFILE",
|
||||
"/run/secrets/openvpn_clientkey",
|
||||
)
|
||||
if err != nil {
|
||||
return settings, fmt.Errorf("cannot read client key file: %w", err)
|
||||
return settings, fmt.Errorf("reading client key file: %w", err)
|
||||
}
|
||||
|
||||
settings.ClientCrt, err = readSecretFileAsStringPtr(
|
||||
settings.EncryptedKey, err = readPEMSecretFile(
|
||||
"OPENVPN_ENCRYPTED_KEY_SECRETFILE",
|
||||
"/run/secrets/openvpn_encrypted_key",
|
||||
)
|
||||
if err != nil {
|
||||
return settings, fmt.Errorf("reading encrypted key file: %w", err)
|
||||
}
|
||||
|
||||
settings.KeyPassphrase, err = readSecretFileAsStringPtr(
|
||||
"OPENVPN_KEY_PASSPHRASE_SECRETFILE",
|
||||
"/run/secrets/openvpn_key_passphrase",
|
||||
)
|
||||
if err != nil {
|
||||
return settings, fmt.Errorf("reading key passphrase file: %w", err)
|
||||
}
|
||||
|
||||
settings.Cert, err = readPEMSecretFile(
|
||||
"OPENVPN_CLIENTCRT_SECRETFILE",
|
||||
"/run/secrets/openvpn_clientcrt",
|
||||
)
|
||||
if err != nil {
|
||||
return settings, fmt.Errorf("cannot read client certificate file: %w", err)
|
||||
return settings, fmt.Errorf("reading client certificate file: %w", err)
|
||||
}
|
||||
|
||||
return settings, nil
|
||||
|
||||
@@ -2,19 +2,17 @@ package secrets
|
||||
|
||||
import (
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gluetun/internal/configuration/sources"
|
||||
)
|
||||
|
||||
var _ sources.Source = (*Reader)(nil)
|
||||
type Source struct{}
|
||||
|
||||
type Reader struct {
|
||||
func New() *Source {
|
||||
return &Source{}
|
||||
}
|
||||
|
||||
func New() *Reader {
|
||||
return &Reader{}
|
||||
}
|
||||
func (s *Source) String() string { return "secret files" }
|
||||
|
||||
func (r *Reader) Read() (settings settings.Settings, err error) {
|
||||
func (s *Source) Read() (settings settings.Settings, err error) {
|
||||
settings.VPN, err = readVPN()
|
||||
if err != nil {
|
||||
return settings, err
|
||||
|
||||
@@ -12,7 +12,7 @@ func readShadowsocks() (settings settings.Shadowsocks, err error) {
|
||||
"/run/secrets/shadowsocks_password",
|
||||
)
|
||||
if err != nil {
|
||||
return settings, fmt.Errorf("cannot read Shadowsocks password secret file: %w", err)
|
||||
return settings, fmt.Errorf("reading Shadowsocks password secret file: %w", err)
|
||||
}
|
||||
|
||||
return settings, nil
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
func readVPN() (vpn settings.VPN, err error) {
|
||||
vpn.OpenVPN, err = readOpenVPN()
|
||||
if err != nil {
|
||||
return vpn, fmt.Errorf("cannot read OpenVPN settings: %w", err)
|
||||
return vpn, fmt.Errorf("reading OpenVPN settings: %w", err)
|
||||
}
|
||||
|
||||
return vpn, nil
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
// 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
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user