Compare commits
127 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1c43a1d55b | ||
|
|
6c639fcf7f | ||
|
|
ec1f252528 | ||
|
|
ee413f59a2 | ||
|
|
d4df87286e | ||
|
|
a194906bdd | ||
|
|
9b00763a69 | ||
|
|
4d627bb7b1 | ||
|
|
dc8fc5f81f | ||
|
|
b787e12e25 | ||
|
|
f96448947f | ||
|
|
e64e5af4c3 | ||
|
|
aa6dc786a4 | ||
|
|
84300db7c1 | ||
|
|
2ac0f35060 | ||
|
|
1a865f56d5 | ||
|
|
0406de399d | ||
|
|
71201411f4 | ||
|
|
c435bbb32c | ||
|
|
4cbfea41f2 | ||
|
|
f9c9ad34f7 | ||
|
|
4ea474b896 | ||
|
|
6aa4a93665 | ||
|
|
ea25a0ff89 | ||
|
|
659da67ed5 | ||
|
|
ffc6d2e593 | ||
|
|
03ce08e23d | ||
|
|
3449e7a0e1 | ||
|
|
c0062fb807 | ||
|
|
1ac031e78c | ||
|
|
e556871e8b | ||
|
|
082a38b769 | ||
|
|
39ae57f49d | ||
|
|
9024912e17 | ||
|
|
eecfb3952f | ||
|
|
0ebfe534d3 | ||
|
|
c5cc240a6c | ||
|
|
1a5a0148ea | ||
|
|
abe2aceb18 | ||
|
|
fa541b8fc2 | ||
|
|
a681d38dfb | ||
|
|
a7b96e3f4d | ||
|
|
04ef92edab | ||
|
|
919b55c3aa | ||
|
|
9c0f187a12 | ||
|
|
075a1e2a80 | ||
|
|
f31a846cda | ||
|
|
9bef46db77 | ||
|
|
d83217f7ac | ||
|
|
1cd2fec796 | ||
|
|
235f24ee5b | ||
|
|
2e34c6009e | ||
|
|
c0eb2f2315 | ||
|
|
8ad16cdc12 | ||
|
|
fae6544431 | ||
|
|
f8a41b2133 | ||
|
|
ff9b56d6d8 | ||
|
|
99d5a591b9 | ||
|
|
fbe252a9b6 | ||
|
|
76a92b90e3 | ||
|
|
2873b06275 | ||
|
|
9cdd6294d2 | ||
|
|
44bc60b00d | ||
|
|
6f0be57860 | ||
|
|
d3d8484b8e | ||
|
|
515ae8efb3 | ||
|
|
83826e1253 | ||
|
|
4292a500ae | ||
|
|
4a0f9c36ba | ||
|
|
ea1991496e | ||
|
|
4675572328 | ||
|
|
412921fc1f | ||
|
|
1c905d0e6f | ||
|
|
2ec9293324 | ||
|
|
9b39a301a8 | ||
|
|
cade2b99bf | ||
|
|
40cdb4f662 | ||
|
|
c58d6d4de2 | ||
|
|
0da2b6ad0b | ||
|
|
37f0e5c73b | ||
|
|
a9cd7be3f9 | ||
|
|
07459ee854 | ||
|
|
943943e8d1 | ||
|
|
5927ee9dec | ||
|
|
3b136e02db | ||
|
|
482447c151 | ||
|
|
5d8fbf8006 | ||
|
|
2ab80771d9 | ||
|
|
7399c00508 | ||
|
|
2d2f657851 | ||
|
|
0e21fdc9de | ||
|
|
b87b2109b1 | ||
|
|
2c30984a10 | ||
|
|
47593928f9 | ||
|
|
b961284845 | ||
|
|
b5d230d47a | ||
|
|
c2972f7bf6 | ||
|
|
aed235f52d | ||
|
|
bfe5e4380f | ||
|
|
eca182a32f | ||
|
|
caabaf918e | ||
|
|
d6924597dd | ||
|
|
c26476a2fd | ||
|
|
5be0d0bbba | ||
|
|
38ddcfa756 | ||
|
|
163ac48ce4 | ||
|
|
def407d610 | ||
|
|
22b2e2cc6e | ||
|
|
c92962e97c | ||
|
|
9d1a0b60a2 | ||
|
|
9cf2c9c4d2 | ||
|
|
e7150ba254 | ||
|
|
7ba70f19ef | ||
|
|
9488a9f88a | ||
|
|
020196f1c3 | ||
|
|
7e325715c7 | ||
|
|
75670a80b8 | ||
|
|
a43973c093 | ||
|
|
1827a03afd | ||
|
|
3100cc1e5e | ||
|
|
eed62fdc6d | ||
|
|
d2b8dbcb10 | ||
|
|
90d43856ef | ||
|
|
86f95cb390 | ||
|
|
3b807e2ca9 | ||
|
|
e8f2296a0d | ||
|
|
1dd38bc658 |
@@ -9,17 +9,21 @@ It works on Linux, Windows and OSX.
|
||||
- [VS code](https://code.visualstudio.com/download) installed
|
||||
- [VS code remote containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) installed
|
||||
- [Docker](https://www.docker.com/products/docker-desktop) installed and running
|
||||
- If you don't use Linux or WSL 2, share your home directory `~/` and the directory of your project with Docker Desktop
|
||||
- [Docker Compose](https://docs.docker.com/compose/install/) installed
|
||||
- Ensure your host has the following and that they are accessible by Docker:
|
||||
- `~/.ssh` directory
|
||||
- `~/.gitconfig` file (can be empty)
|
||||
|
||||
## Setup
|
||||
|
||||
1. Create the following files on your host if you don't have them:
|
||||
|
||||
```sh
|
||||
touch ~/.gitconfig ~/.zsh_history
|
||||
```
|
||||
|
||||
Note that the development container will create the empty directories `~/.docker`, `~/.ssh` and `~/.kube` if you don't have them.
|
||||
|
||||
1. **For Docker on OSX or Windows without WSL**: ensure your home directory `~` is accessible by Docker.
|
||||
1. Open the command palette in Visual Studio Code (CTRL+SHIFT+P).
|
||||
1. Select `Remote-Containers: Open Folder in Container...` and choose the project directory.
|
||||
1. For Docker running on Windows HyperV, if you want to use SSH keys, bind mount them at `/tmp/.ssh` by changing the `volumes` section in the [docker-compose.yml](docker-compose.yml).
|
||||
|
||||
## Customization
|
||||
|
||||
@@ -29,13 +33,9 @@ You can make changes to the [Dockerfile](Dockerfile) and then rebuild the image.
|
||||
|
||||
```Dockerfile
|
||||
FROM qmcgaw/godevcontainer
|
||||
USER root
|
||||
RUN apk add curl
|
||||
USER vscode
|
||||
```
|
||||
|
||||
Note that you may need to use `USER root` to build as root, and then change back to `USER vscode`.
|
||||
|
||||
To rebuild the image, either:
|
||||
|
||||
- With VSCode through the command palette, select `Remote-Containers: Rebuild and reopen in container`
|
||||
@@ -47,11 +47,11 @@ You can customize **settings** and **extensions** in the [devcontainer.json](dev
|
||||
|
||||
### Entrypoint script
|
||||
|
||||
You can bind mount a shell script to `/home/vscode/.welcome.sh` to replace the [current welcome script](shell/.welcome.sh).
|
||||
You can bind mount a shell script to `/root/.welcome.sh` to replace the [current welcome script](https://github.com/qdm12/godevcontainer/blob/master/shell/.welcome.sh).
|
||||
|
||||
### Publish a port
|
||||
|
||||
To access a port from your host to your development container, publish a port in [docker-compose.yml](docker-compose.yml).
|
||||
To access a port from your host to your development container, publish a port in [docker-compose.yml](docker-compose.yml). You can also now do it directly with VSCode without restarting the container.
|
||||
|
||||
### Run other services
|
||||
|
||||
|
||||
@@ -1,82 +1,73 @@
|
||||
{
|
||||
"name": "gluetun-dev",
|
||||
"dockerComposeFile": [
|
||||
"docker-compose.yml"
|
||||
],
|
||||
"service": "vscode",
|
||||
"runServices": [
|
||||
"vscode"
|
||||
],
|
||||
"shutdownAction": "stopCompose",
|
||||
"postCreateCommand": "source ~/.windows.sh && go mod download && go mod tidy",
|
||||
"workspaceFolder": "/workspace",
|
||||
"extensions": [
|
||||
"golang.go",
|
||||
"eamodio.gitlens", // IDE Git information
|
||||
"davidanson.vscode-markdownlint",
|
||||
"ms-azuretools.vscode-docker", // Docker integration and linting
|
||||
"shardulm94.trailing-spaces", // Show trailing spaces
|
||||
"Gruntfuggly.todo-tree", // Highlights TODO comments
|
||||
"bierner.emojisense", // Emoji sense for markdown
|
||||
"stkb.rewrap", // rewrap comments after n characters on one line
|
||||
"vscode-icons-team.vscode-icons", // Better file extension icons
|
||||
"github.vscode-pull-request-github", // Github interaction
|
||||
"redhat.vscode-yaml", // Kubernetes, Drone syntax highlighting
|
||||
"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",
|
||||
"remote.extensionKind": {
|
||||
"ms-azuretools.vscode-docker": "workspace"
|
||||
},
|
||||
"editor.codeActionsOnSaveTimeout": 3000,
|
||||
"go.useLanguageServer": true,
|
||||
"[go]": {
|
||||
"editor.formatOnSave": true,
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.organizeImports": true,
|
||||
},
|
||||
// Optional: Disable snippets, as they conflict with completion ranking.
|
||||
"editor.snippetSuggestions": "none"
|
||||
},
|
||||
"[go.mod]": {
|
||||
"editor.formatOnSave": true,
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.organizeImports": true,
|
||||
},
|
||||
},
|
||||
"gopls": {
|
||||
"usePlaceholders": false,
|
||||
"staticcheck": true
|
||||
},
|
||||
"go.autocompleteUnimportedPackages": true,
|
||||
"go.gotoSymbol.includeImports": true,
|
||||
"go.gotoSymbol.includeGoroot": true,
|
||||
"go.lintTool": "golangci-lint",
|
||||
"go.buildOnSave": "workspace",
|
||||
"go.lintOnSave": "workspace",
|
||||
"go.vetOnSave": "workspace",
|
||||
"editor.formatOnSave": true,
|
||||
"go.toolsEnvVars": {
|
||||
"GOFLAGS": "-tags=",
|
||||
// "CGO_ENABLED": 1 // for the race detector
|
||||
},
|
||||
"gopls.env": {
|
||||
"GOFLAGS": "-tags="
|
||||
},
|
||||
"go.testEnvVars": {
|
||||
"": ""
|
||||
},
|
||||
"go.testFlags": [
|
||||
"-v",
|
||||
// "-race"
|
||||
],
|
||||
"go.testTimeout": "10s",
|
||||
"go.coverOnSingleTest": true,
|
||||
"go.coverOnSingleTestFile": true,
|
||||
"go.coverOnTestPackage": true
|
||||
}
|
||||
{
|
||||
"name": "gluetun-dev",
|
||||
"dockerComposeFile": [
|
||||
"docker-compose.yml"
|
||||
],
|
||||
"service": "vscode",
|
||||
"runServices": [
|
||||
"vscode"
|
||||
],
|
||||
"shutdownAction": "stopCompose",
|
||||
"postCreateCommand": "~/.windows.sh && go mod download && go mod tidy",
|
||||
"workspaceFolder": "/workspace",
|
||||
// "overrideCommand": "",
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"extensions": [
|
||||
"golang.go",
|
||||
"eamodio.gitlens", // IDE Git information
|
||||
"davidanson.vscode-markdownlint",
|
||||
"ms-azuretools.vscode-docker", // Docker integration and linting
|
||||
"shardulm94.trailing-spaces", // Show trailing spaces
|
||||
"Gruntfuggly.todo-tree", // Highlights TODO comments
|
||||
"bierner.emojisense", // Emoji sense for markdown
|
||||
"stkb.rewrap", // rewrap comments after n characters on one line
|
||||
"vscode-icons-team.vscode-icons", // Better file extension icons
|
||||
"github.vscode-pull-request-github", // Github interaction
|
||||
"redhat.vscode-yaml", // Kubernetes, Drone syntax highlighting
|
||||
"bajdzis.vscode-database", // Supports connections to mysql or postgres, over SSL, socked
|
||||
"IBM.output-colorizer", // Colorize your output/test logs
|
||||
"github.copilot" // AI code completion
|
||||
],
|
||||
"settings": {
|
||||
"files.eol": "\n",
|
||||
"remote.extensionKind": {
|
||||
"ms-azuretools.vscode-docker": "workspace"
|
||||
},
|
||||
"go.useLanguageServer": true,
|
||||
"[go]": {
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.organizeImports": true
|
||||
}
|
||||
},
|
||||
"[go.mod]": {
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.organizeImports": true
|
||||
}
|
||||
},
|
||||
"gopls": {
|
||||
"usePlaceholders": false,
|
||||
"staticcheck": true
|
||||
},
|
||||
"go.lintTool": "golangci-lint",
|
||||
"go.lintOnSave": "package",
|
||||
"editor.formatOnSave": true,
|
||||
"go.buildTags": "linux",
|
||||
"go.toolsEnvVars": {
|
||||
"CGO_ENABLED": "0"
|
||||
},
|
||||
"go.testEnvVars": {
|
||||
"CGO_ENABLED": "1"
|
||||
},
|
||||
"go.testFlags": [
|
||||
"-v",
|
||||
"-race"
|
||||
],
|
||||
"go.testTimeout": "10s",
|
||||
"go.coverOnSingleTest": true,
|
||||
"go.coverOnSingleTestFile": true,
|
||||
"go.coverOnTestPackage": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,30 +1,28 @@
|
||||
version: "3.7"
|
||||
|
||||
services:
|
||||
vscode:
|
||||
build: .
|
||||
devices:
|
||||
- /dev/net/tun:/dev/net/tun
|
||||
volumes:
|
||||
- ../:/workspace
|
||||
# Docker socket to access Docker server
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
# Docker configuration
|
||||
- ~/.docker:/root/.docker
|
||||
# SSH directory for Linux, OSX and WSL
|
||||
# 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
|
||||
environment:
|
||||
- TZ=
|
||||
cap_add:
|
||||
# For debugging with dlv
|
||||
# - SYS_PTRACE
|
||||
- NET_ADMIN
|
||||
security_opt:
|
||||
# For debugging with dlv
|
||||
- seccomp:unconfined
|
||||
entrypoint: zsh -c "while sleep 1000; do :; done"
|
||||
version: "3.7"
|
||||
|
||||
services:
|
||||
vscode:
|
||||
build: .
|
||||
volumes:
|
||||
- ../:/workspace
|
||||
# Docker socket to access Docker server
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
# SSH directory for Linux, OSX and WSL
|
||||
# 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
|
||||
# Git config
|
||||
- ~/.gitconfig:/root/.gitconfig
|
||||
environment:
|
||||
- TZ=
|
||||
cap_add:
|
||||
# For debugging with dlv
|
||||
- SYS_PTRACE
|
||||
- NET_ADMIN
|
||||
security_opt:
|
||||
# For debugging with dlv
|
||||
- seccomp:unconfined
|
||||
entrypoint: [ "zsh", "-c", "while sleep 1000; do :; done" ]
|
||||
|
||||
2
.github/CONTRIBUTING.md
vendored
2
.github/CONTRIBUTING.md
vendored
@@ -13,6 +13,6 @@ Contributions are [released](https://help.github.com/articles/github-terms-of-se
|
||||
|
||||
## Resources
|
||||
|
||||
- [Gluetun guide on development](https://github.com/qdm12/gluetun/wiki/Development)
|
||||
- [Gluetun guide on development](https://github.com/qdm12/gluetun-wiki/blob/main/contributing/development.md)
|
||||
- [Using Pull Requests](https://help.github.com/articles/about-pull-requests/)
|
||||
- [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/)
|
||||
|
||||
12
.github/ISSUE_TEMPLATE/bug.yml
vendored
12
.github/ISSUE_TEMPLATE/bug.yml
vendored
@@ -7,13 +7,18 @@ body:
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to fill out this bug report!
|
||||
|
||||
⚠️ Your issue will be instantly closed as not planned WITHOUT explanation if:
|
||||
- you do not fill out **the title of the issue** ☝️
|
||||
- you do not provide the **Gluetun version** as requested below
|
||||
- you provide **less than 10 lines of logs** as requested below
|
||||
- type: dropdown
|
||||
id: urgent
|
||||
attributes:
|
||||
label: Is this urgent?
|
||||
description: |
|
||||
Is this a critical bug, or do you need this fixed urgently?
|
||||
If this is urgent, note you can use one of the [image tags available](https://github.com/qdm12/gluetun/wiki/Docker-image-tags) if that can help.
|
||||
If this is urgent, note you can use one of the [image tags available](https://github.com/qdm12/gluetun-wiki/blob/main/setup/docker-image-tags.md) if that can help.
|
||||
options:
|
||||
- "No"
|
||||
- "Yes"
|
||||
@@ -75,6 +80,7 @@ body:
|
||||
- Portainer
|
||||
- Kubernetes
|
||||
- Podman
|
||||
- Unraid
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
@@ -84,7 +90,7 @@ body:
|
||||
label: What is the version of Gluetun
|
||||
description: |
|
||||
Copy paste the version line at the top of your logs.
|
||||
It should be in the form `Running version latest built on 2020-03-13T01:30:06Z (commit d0f678c)`.
|
||||
It MUST be in the form `Running version latest built on 2020-03-13T01:30:06Z (commit d0f678c)`.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
@@ -97,7 +103,7 @@ body:
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: Share your logs
|
||||
label: Share your logs (at least 10 lines)
|
||||
description: No sensitive information is logged out except when running with `LOG_LEVEL=debug`.
|
||||
render: plain text
|
||||
validations:
|
||||
|
||||
3
.github/ISSUE_TEMPLATE/config.yml
vendored
3
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,4 +1,7 @@
|
||||
contact_links:
|
||||
- name: Report a Wiki issue
|
||||
url: https://github.com/qdm12/gluetun-wiki/issues/new
|
||||
about: Please create an issue on the gluetun-wiki repository.
|
||||
- name: Configuration help?
|
||||
url: https://github.com/qdm12/gluetun/discussions/new
|
||||
about: Please create a Github discussion.
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/provider.md
vendored
2
.github/ISSUE_TEMPLATE/provider.md
vendored
@@ -14,4 +14,4 @@ One of the following is required:
|
||||
|
||||
If the list of servers requires to login **or** is hidden behind an interactive configurator,
|
||||
you can only use a custom Openvpn configuration file.
|
||||
[The Wiki](https://github.com/qdm12/gluetun/wiki/Openvpn-file) describes how to do so.
|
||||
[The Wiki's OpenVPN configuration file page](https://github.com/qdm12/gluetun-wiki/blob/main/setup/openvpn-configuration-file.md) describes how to do so.
|
||||
|
||||
18
.github/ISSUE_TEMPLATE/wiki issue.yml
vendored
18
.github/ISSUE_TEMPLATE/wiki issue.yml
vendored
@@ -1,18 +0,0 @@
|
||||
name: Wiki issue
|
||||
description: Report a Wiki issue
|
||||
title: "Wiki issue: "
|
||||
labels: ["📄 Wiki issue"]
|
||||
body:
|
||||
- type: input
|
||||
id: url
|
||||
attributes:
|
||||
label: "URL to the Wiki page"
|
||||
placeholder: "https://github.com/qdm12/gluetun/wiki/OpenVPN-options"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: "What's the issue?"
|
||||
validations:
|
||||
required: true
|
||||
12
.github/labels.yml
vendored
12
.github/labels.yml
vendored
@@ -95,6 +95,9 @@
|
||||
description: ""
|
||||
|
||||
# Problem category
|
||||
- name: "Config problem"
|
||||
color: "ffc7ea"
|
||||
description: ""
|
||||
- name: "Openvpn"
|
||||
color: "ffc7ea"
|
||||
description: ""
|
||||
@@ -107,6 +110,15 @@
|
||||
- name: "Firewall"
|
||||
color: "ffc7ea"
|
||||
description: ""
|
||||
- name: "Routing"
|
||||
color: "ffc7ea"
|
||||
description: ""
|
||||
- name: "IPv6"
|
||||
color: "ffc7ea"
|
||||
description: ""
|
||||
- name: "Port forwarding"
|
||||
color: "ffc7ea"
|
||||
description: ""
|
||||
- name: "HTTP proxy"
|
||||
color: "ffc7ea"
|
||||
description: ""
|
||||
|
||||
2
.github/workflows/ci-skip.yml
vendored
2
.github/workflows/ci-skip.yml
vendored
@@ -14,8 +14,6 @@ on:
|
||||
- go.mod
|
||||
- go.sum
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
paths-ignore:
|
||||
- .github/workflows/ci.yml
|
||||
- cmd/**
|
||||
|
||||
5
.github/workflows/ci.yml
vendored
5
.github/workflows/ci.yml
vendored
@@ -17,8 +17,6 @@ on:
|
||||
- go.mod
|
||||
- go.sum
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- .github/workflows/ci.yml
|
||||
- cmd/**
|
||||
@@ -47,6 +45,7 @@ jobs:
|
||||
level: error
|
||||
exclude: |
|
||||
./internal/storage/servers.json
|
||||
*.md
|
||||
|
||||
- name: Linting
|
||||
run: docker build --target lint .
|
||||
@@ -136,7 +135,7 @@ jobs:
|
||||
run: echo "::set-output name=value::$(git rev-parse --short HEAD)"
|
||||
|
||||
- name: Build and push final image
|
||||
uses: docker/build-push-action@v4.0.0
|
||||
uses: docker/build-push-action@v4.1.1
|
||||
with:
|
||||
platforms: linux/amd64,linux/386,linux/arm64,linux/arm/v6,linux/arm/v7,linux/ppc64le
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
|
||||
25
.github/workflows/dockerhub-description.yml
vendored
25
.github/workflows/dockerhub-description.yml
vendored
@@ -1,25 +0,0 @@
|
||||
name: Docker Hub description
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- README.md
|
||||
- .github/workflows/dockerhub-description.yml
|
||||
jobs:
|
||||
docker-hub-description:
|
||||
if: github.repository == 'qdm12/gluetun'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: peter-evans/dockerhub-description@v3
|
||||
with:
|
||||
username: qmcgaw
|
||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
repository: qmcgaw/gluetun
|
||||
short-description: Lightweight Swiss-knife VPN client to connect to several VPN providers
|
||||
readme-filepath: README.md
|
||||
21
.github/workflows/markdown-skip.yml
vendored
Normal file
21
.github/workflows/markdown-skip.yml
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
name: Markdown
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths-ignore:
|
||||
- "**.md"
|
||||
- .github/workflows/markdown.yml
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- "**.md"
|
||||
- .github/workflows/markdown.yml
|
||||
|
||||
jobs:
|
||||
markdown:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
steps:
|
||||
- name: No trigger path triggered for required markdown workflow.
|
||||
run: exit 0
|
||||
46
.github/workflows/markdown.yml
vendored
Normal file
46
.github/workflows/markdown.yml
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
name: Markdown
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- "**.md"
|
||||
- .github/workflows/markdown.yml
|
||||
pull_request:
|
||||
paths:
|
||||
- "**.md"
|
||||
- .github/workflows/markdown.yml
|
||||
|
||||
jobs:
|
||||
markdown:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: DavidAnson/markdownlint-cli2-action@v11
|
||||
with:
|
||||
globs: "**.md"
|
||||
config: .markdownlint.json
|
||||
|
||||
- uses: reviewdog/action-misspell@v1
|
||||
with:
|
||||
locale: "US"
|
||||
level: error
|
||||
pattern: |
|
||||
*.md
|
||||
|
||||
- uses: gaurav-nelson/github-action-markdown-link-check@v1
|
||||
with:
|
||||
use-quiet-mode: yes
|
||||
|
||||
- uses: peter-evans/dockerhub-description@v3
|
||||
if: github.repository == 'qdm12/gluetun' && github.event_name == 'push'
|
||||
with:
|
||||
username: qmcgaw
|
||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
repository: qmcgaw/gluetun
|
||||
short-description: Lightweight Swiss-knife VPN client to connect to several VPN providers
|
||||
readme-filepath: README.md
|
||||
@@ -46,6 +46,7 @@ linters:
|
||||
- decorder
|
||||
- dogsled
|
||||
- dupl
|
||||
- dupword
|
||||
- durationcheck
|
||||
- errchkjson
|
||||
- errname
|
||||
@@ -54,6 +55,7 @@ linters:
|
||||
- exportloopref
|
||||
- forcetypeassert
|
||||
- gci
|
||||
- gocheckcompilerdirectives
|
||||
- gochecknoglobals
|
||||
- gochecknoinits
|
||||
- gocognit
|
||||
@@ -68,6 +70,7 @@ linters:
|
||||
- gomoddirectives
|
||||
- goprintffuncname
|
||||
- gosec
|
||||
- gosmopolitan
|
||||
- grouper
|
||||
- importas
|
||||
- interfacebloat
|
||||
@@ -75,7 +78,9 @@ linters:
|
||||
- lll
|
||||
- maintidx
|
||||
- makezero
|
||||
- mirror
|
||||
- misspell
|
||||
- musttag
|
||||
- nakedret
|
||||
- nestif
|
||||
- nilerr
|
||||
@@ -83,6 +88,7 @@ linters:
|
||||
- noctx
|
||||
- nolintlint
|
||||
- nosprintfhostport
|
||||
- paralleltest
|
||||
- prealloc
|
||||
- predeclared
|
||||
- promlinter
|
||||
@@ -90,6 +96,7 @@ linters:
|
||||
- revive
|
||||
- rowserrcheck
|
||||
- sqlclosecheck
|
||||
- tagalign
|
||||
- tenv
|
||||
- thelper
|
||||
- tparallel
|
||||
@@ -98,6 +105,7 @@ linters:
|
||||
- usestdlibvars
|
||||
- wastedassign
|
||||
- whitespace
|
||||
- zerologlint
|
||||
|
||||
run:
|
||||
skip-dirs:
|
||||
|
||||
3
.markdownlint.json
Normal file
3
.markdownlint.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"MD013": false
|
||||
}
|
||||
8
.vscode/extensions.json
vendored
Normal file
8
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
// This list should be kept to the strict minimum
|
||||
// to develop this project.
|
||||
"recommendations": [
|
||||
"golang.go",
|
||||
"davidanson.vscode-markdownlint",
|
||||
],
|
||||
}
|
||||
29
.vscode/settings.json
vendored
Normal file
29
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
// The settings should be kept to the strict minimum
|
||||
// to develop this project.
|
||||
"files.eol": "\n",
|
||||
"editor.formatOnSave": true,
|
||||
"go.buildTags": "linux",
|
||||
"go.toolsEnvVars": {
|
||||
"CGO_ENABLED": "0"
|
||||
},
|
||||
"go.testEnvVars": {
|
||||
"CGO_ENABLED": "1"
|
||||
},
|
||||
"go.testFlags": [
|
||||
"-v",
|
||||
"-race"
|
||||
],
|
||||
"go.testTimeout": "10s",
|
||||
"go.coverOnSingleTest": true,
|
||||
"go.coverOnSingleTestFile": true,
|
||||
"go.coverOnTestPackage": true,
|
||||
"go.useLanguageServer": true,
|
||||
"[go]": {
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.organizeImports": true
|
||||
}
|
||||
},
|
||||
"go.lintTool": "golangci-lint",
|
||||
"go.lintOnSave": "package"
|
||||
}
|
||||
22
Dockerfile
22
Dockerfile
@@ -1,8 +1,8 @@
|
||||
ARG ALPINE_VERSION=3.17
|
||||
ARG GO_ALPINE_VERSION=3.17
|
||||
ARG GO_VERSION=1.20
|
||||
ARG ALPINE_VERSION=3.18
|
||||
ARG GO_ALPINE_VERSION=3.18
|
||||
ARG GO_VERSION=1.21
|
||||
ARG XCPUTRANSLATE_VERSION=v0.6.0
|
||||
ARG GOLANGCI_LINT_VERSION=v1.52.2
|
||||
ARG GOLANGCI_LINT_VERSION=v1.54.1
|
||||
ARG MOCKGEN_VERSION=v1.6.0
|
||||
ARG BUILDPLATFORM=linux/amd64
|
||||
|
||||
@@ -90,13 +90,15 @@ ENV VPN_SERVICE_PROVIDER=pia \
|
||||
OPENVPN_FLAGS= \
|
||||
OPENVPN_CIPHERS= \
|
||||
OPENVPN_AUTH= \
|
||||
OPENVPN_PROCESS_USER= \
|
||||
OPENVPN_PROCESS_USER=root \
|
||||
OPENVPN_CUSTOM_CONFIG= \
|
||||
# Wireguard
|
||||
WIREGUARD_PRIVATE_KEY= \
|
||||
WIREGUARD_PRESHARED_KEY= \
|
||||
WIREGUARD_PUBLIC_KEY= \
|
||||
WIREGUARD_ALLOWED_IPS= \
|
||||
WIREGUARD_ADDRESSES= \
|
||||
WIREGUARD_MTU=1400 \
|
||||
WIREGUARD_IMPLEMENTATION=auto \
|
||||
# VPN server filtering
|
||||
SERVER_REGIONS= \
|
||||
@@ -109,6 +111,7 @@ ENV VPN_SERVICE_PROVIDER=pia \
|
||||
# # Private Internet Access only:
|
||||
PRIVATE_INTERNET_ACCESS_OPENVPN_ENCRYPTION_PRESET= \
|
||||
VPN_PORT_FORWARDING=off \
|
||||
VPN_PORT_FORWARDING_PROVIDER= \
|
||||
VPN_PORT_FORWARDING_STATUS_FILE="/tmp/gluetun/forwarded_port" \
|
||||
# # Cyberghost only:
|
||||
OPENVPN_CERT= \
|
||||
@@ -164,6 +167,7 @@ ENV VPN_SERVICE_PROVIDER=pia \
|
||||
HTTPPROXY= \
|
||||
HTTPPROXY_LOG=off \
|
||||
HTTPPROXY_LISTENING_ADDRESS=":8888" \
|
||||
HTTPPROXY_STEALTH=off \
|
||||
HTTPPROXY_USER= \
|
||||
HTTPPROXY_PASSWORD= \
|
||||
HTTPPROXY_USER_SECRETFILE=/run/secrets/httpproxy_user \
|
||||
@@ -176,6 +180,7 @@ ENV VPN_SERVICE_PROVIDER=pia \
|
||||
SHADOWSOCKS_PASSWORD_SECRETFILE=/run/secrets/shadowsocks_password \
|
||||
SHADOWSOCKS_CIPHER=chacha20-ietf-poly1305 \
|
||||
# Control server
|
||||
HTTP_CONTROL_SERVER_LOG=on \
|
||||
HTTP_CONTROL_SERVER_ADDRESS=":8000" \
|
||||
# Server data updater
|
||||
UPDATER_PERIOD=0 \
|
||||
@@ -199,12 +204,11 @@ 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 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 add --no-cache --update -X "https://dl-cdn.alpinelinux.org/alpine/v3.17/main" openvpn\~2.5 && \
|
||||
mv /usr/sbin/openvpn /usr/sbin/openvpn2.5 && \
|
||||
apk del openvpn && \
|
||||
apk add --no-cache --update openvpn ca-certificates iptables ip6tables unbound tzdata && \
|
||||
mv /usr/sbin/openvpn /usr/sbin/openvpn2.5 && \
|
||||
mv /usr/sbin/openvpn /usr/sbin/openvpn2.6 && \
|
||||
# Fix vulnerability issue
|
||||
apk add --no-cache --update busybox && \
|
||||
rm -rf /var/cache/apk/* /etc/unbound/* /usr/sbin/unbound-* /etc/openvpn/*.sh /usr/lib/openvpn/plugins/openvpn-plugin-down-root.so && \
|
||||
|
||||
38
README.md
38
README.md
@@ -35,20 +35,19 @@ Lightweight swiss-knife-like VPN client to multiple VPN service providers
|
||||
|
||||
## Quick links
|
||||
|
||||
- [Setup](#Setup)
|
||||
- [Features](#Features)
|
||||
- [Setup](#setup)
|
||||
- [Features](#features)
|
||||
- Problem?
|
||||
- [Check the Wiki](https://github.com/qdm12/gluetun/wiki)
|
||||
- Check the Wiki [common errors](https://github.com/qdm12/gluetun-wiki/tree/main/errors) and [faq](https://github.com/qdm12/gluetun-wiki/tree/main/faq)
|
||||
- [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)
|
||||
- **Want to add a VPN provider?** check [the development page](https://github.com/qdm12/gluetun-wiki/blob/main/contributing/development.md) and [add a provider page](https://github.com/qdm12/gluetun-wiki/blob/main/contributing/add-a-provider.md)
|
||||
- Video:
|
||||
|
||||
[](https://youtu.be/0F6I03LQcI4)
|
||||
@@ -57,13 +56,13 @@ Lightweight swiss-knife-like VPN client to multiple VPN service providers
|
||||
|
||||
## Features
|
||||
|
||||
- Based on Alpine 3.17 for a small Docker image of 42MB
|
||||
- Based on Alpine 3.18 for a small Docker image of 35.6MB
|
||||
- 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)
|
||||
- For **AirVPN**, **Ivpn**, **Mullvad**, **NordVPN**, **Surfshark** and **Windscribe**
|
||||
- For **ProtonVPN**, **PureVPN**, **Torguard**, **VPN Unlimited** and **WeVPN** using [the custom provider](https://github.com/qdm12/gluetun-wiki/blob/main/setup/providers/custom.md)
|
||||
- For custom Wireguard configurations using [the custom provider](https://github.com/qdm12/gluetun-wiki/blob/main/setup/providers/custom.md)
|
||||
- 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
|
||||
@@ -71,10 +70,10 @@ Lightweight swiss-knife-like VPN client to multiple VPN service providers
|
||||
- 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)
|
||||
- [Connect other containers to it](https://github.com/qdm12/gluetun-wiki/blob/main/setup/connect-a-container-to-gluetun.md)
|
||||
- [Connect LAN devices to it](https://github.com/qdm12/gluetun-wiki/blob/main/setup/connect-a-lan-device-to-gluetun.md)
|
||||
- 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)
|
||||
- [Custom VPN server side port forwarding for Private Internet Access](https://github.com/qdm12/gluetun-wiki/blob/main/setup/providers/private-internet-access.md#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
|
||||
@@ -83,9 +82,9 @@ Lightweight swiss-knife-like VPN client to multiple VPN service providers
|
||||
|
||||
🎉 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)!
|
||||
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+)
|
||||
[🐛 Found a bug in the Wiki?!](https://github.com/qdm12/gluetun-wiki/issues/new)
|
||||
|
||||
Here's a docker-compose.yml for the laziest:
|
||||
|
||||
@@ -95,7 +94,8 @@ 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
|
||||
# line above must be uncommented to allow external containers to connect.
|
||||
# See https://github.com/qdm12/gluetun-wiki/blob/main/setup/connect-a-container-to-gluetun.md#external-container-to-gluetun
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
devices:
|
||||
@@ -107,7 +107,7 @@ services:
|
||||
volumes:
|
||||
- /yourpath:/gluetun
|
||||
environment:
|
||||
# See https://github.com/qdm12/gluetun/wiki
|
||||
# See https://github.com/qdm12/gluetun-wiki/tree/main/setup#setup
|
||||
- VPN_SERVICE_PROVIDER=ivpn
|
||||
- VPN_TYPE=openvpn
|
||||
# OpenVPN:
|
||||
@@ -118,13 +118,13 @@ services:
|
||||
# - 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
|
||||
# Server list updater
|
||||
# See https://github.com/qdm12/gluetun-wiki/blob/main/setup/servers.md#update-the-vpn-servers-list
|
||||
- UPDATER_PERIOD=
|
||||
- UPDATER_VPN_SERVICE_PROVIDERS=
|
||||
```
|
||||
|
||||
🆕 Image also available as `ghcr.io/qdm12/gluetun`
|
||||
|
||||
## License
|
||||
|
||||
[](https://github.com/qdm12/gluetun/master/LICENSE)
|
||||
[](https://github.com/qdm12/gluetun/blob/master/LICENSE)
|
||||
|
||||
@@ -82,10 +82,10 @@ func main() {
|
||||
cli := cli.New()
|
||||
cmder := command.NewCmder()
|
||||
|
||||
envReader := env.New(logger)
|
||||
filesReader := files.New()
|
||||
secretsReader := secrets.New()
|
||||
muxReader := mux.New(envReader, filesReader, secretsReader)
|
||||
filesReader := files.New()
|
||||
envReader := env.New(logger)
|
||||
muxReader := mux.New(secretsReader, filesReader, envReader)
|
||||
|
||||
errorCh := make(chan error)
|
||||
go func() {
|
||||
@@ -159,7 +159,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
||||
}
|
||||
}
|
||||
|
||||
announcementExp, err := time.Parse(time.RFC3339, "2021-02-15T00:00:00Z")
|
||||
announcementExp, err := time.Parse(time.RFC3339, "2023-07-01T00:00:00Z")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -170,7 +170,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
||||
Version: buildInfo.Version,
|
||||
Commit: buildInfo.Commit,
|
||||
BuildDate: buildInfo.Created,
|
||||
Announcement: "Large settings parsing refactoring merged on 2022-01-06, please report any issue!",
|
||||
Announcement: "Wiki moved to https://github.com/qdm12/gluetun-wiki",
|
||||
AnnounceExp: announcementExp,
|
||||
// Sponsor information
|
||||
PaypalUser: "qmcgaw",
|
||||
@@ -264,8 +264,8 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
||||
|
||||
err = printVersions(ctx, logger, []printVersionElement{
|
||||
{name: "Alpine", getVersion: alpineConf.Version},
|
||||
{name: "OpenVPN 2.4", getVersion: ovpnConf.Version24},
|
||||
{name: "OpenVPN 2.5", getVersion: ovpnConf.Version25},
|
||||
{name: "OpenVPN 2.6", getVersion: ovpnConf.Version26},
|
||||
{name: "Unbound", getVersion: dnsConf.Version},
|
||||
{name: "IPtables", getVersion: func(ctx context.Context) (version string, err error) {
|
||||
return firewall.Version(ctx, cmder)
|
||||
@@ -376,12 +376,13 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
||||
|
||||
portForwardLogger := logger.New(log.SetComponent("port forwarding"))
|
||||
portForwardLooper := portforward.NewLoop(allSettings.VPN.Provider.PortForwarding,
|
||||
httpClient, firewallConf, portForwardLogger, puid, pgid)
|
||||
portForwardHandler, portForwardCtx, portForwardDone := goshutdown.NewGoRoutineHandler(
|
||||
"port forwarding", goroutine.OptionTimeout(time.Second))
|
||||
go portForwardLooper.Run(portForwardCtx, portForwardDone)
|
||||
routingConf, httpClient, firewallConf, portForwardLogger, puid, pgid)
|
||||
portForwardRunError, err := portForwardLooper.Start(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("starting port forwarding loop: %w", err)
|
||||
}
|
||||
|
||||
unboundLogger := logger.New(log.SetComponent("dns over tls"))
|
||||
unboundLogger := logger.New(log.SetComponent("dns"))
|
||||
unboundLooper := dns.NewLoop(dnsConf, allSettings.DNS, httpClient,
|
||||
unboundLogger)
|
||||
dnsHandler, dnsCtx, dnsDone := goshutdown.NewGoRoutineHandler(
|
||||
@@ -399,15 +400,10 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
||||
publicIPLooper := publicip.NewLoop(ipFetcher,
|
||||
logger.New(log.SetComponent("ip getter")),
|
||||
allSettings.PublicIP, puid, pgid)
|
||||
pubIPHandler, pubIPCtx, pubIPDone := goshutdown.NewGoRoutineHandler(
|
||||
"public IP", goroutine.OptionTimeout(defaultShutdownTimeout))
|
||||
go publicIPLooper.Run(pubIPCtx, pubIPDone)
|
||||
otherGroupHandler.Add(pubIPHandler)
|
||||
|
||||
pubIPTickerHandler, pubIPTickerCtx, pubIPTickerDone := goshutdown.NewGoRoutineHandler(
|
||||
"public IP", goroutine.OptionTimeout(defaultShutdownTimeout))
|
||||
go publicIPLooper.RunRestartTicker(pubIPTickerCtx, pubIPTickerDone)
|
||||
tickersGroupHandler.Add(pubIPTickerHandler)
|
||||
publicIPRunError, err := publicIPLooper.Start(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("starting public ip loop: %w", err)
|
||||
}
|
||||
|
||||
updaterLogger := logger.New(log.SetComponent("updater"))
|
||||
|
||||
@@ -481,13 +477,31 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
||||
order.OptionOnSuccess(defaultShutdownOnSuccess),
|
||||
order.OptionOnFailure(defaultShutdownOnFailure))
|
||||
orderHandler.Append(controlGroupHandler, tickersGroupHandler, healthServerHandler,
|
||||
vpnHandler, portForwardHandler, otherGroupHandler)
|
||||
vpnHandler, otherGroupHandler)
|
||||
|
||||
// Start VPN for the first time in a blocking call
|
||||
// until the VPN is launched
|
||||
_, _ = vpnLooper.ApplyStatus(ctx, constants.Running) // TODO option to disable with variable
|
||||
|
||||
<-ctx.Done()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
stoppers := []interface {
|
||||
String() string
|
||||
Stop() error
|
||||
}{
|
||||
portForwardLooper, publicIPLooper,
|
||||
}
|
||||
for _, stopper := range stoppers {
|
||||
err := stopper.Stop()
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("stopping %s: %s", stopper, err))
|
||||
}
|
||||
}
|
||||
case err := <-portForwardRunError:
|
||||
logger.Errorf("port forwarding loop crashed: %s", err)
|
||||
case err := <-publicIPRunError:
|
||||
logger.Errorf("public IP loop crashed: %s", err)
|
||||
}
|
||||
|
||||
return orderHandler.Shutdown(context.Background())
|
||||
}
|
||||
@@ -531,30 +545,29 @@ type netLinker interface {
|
||||
type Addresser interface {
|
||||
AddrList(link netlink.Link, family int) (
|
||||
addresses []netlink.Addr, err error)
|
||||
AddrAdd(link netlink.Link, addr *netlink.Addr) error
|
||||
AddrReplace(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
|
||||
RouteList(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
|
||||
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)
|
||||
LinkAdd(link netlink.Link) (linkIndex int, err error)
|
||||
LinkDel(link netlink.Link) (err error)
|
||||
LinkSetUp(link netlink.Link) (err error)
|
||||
LinkSetUp(link netlink.Link) (linkIndex int, err error)
|
||||
LinkSetDown(link netlink.Link) (err error)
|
||||
}
|
||||
|
||||
|
||||
27
go.mod
27
go.mod
@@ -1,29 +1,33 @@
|
||||
module github.com/qdm12/gluetun
|
||||
|
||||
go 1.20
|
||||
go 1.21
|
||||
|
||||
require (
|
||||
github.com/breml/rootcerts v0.2.10
|
||||
github.com/breml/rootcerts v0.2.11
|
||||
github.com/fatih/color v1.15.0
|
||||
github.com/golang/mock v1.6.0
|
||||
github.com/klauspost/compress v1.16.7
|
||||
github.com/klauspost/pgzip v1.2.6
|
||||
github.com/qdm12/dns v1.11.0
|
||||
github.com/qdm12/golibs v0.0.0-20210822203818-5c568b0777b6
|
||||
github.com/qdm12/gosettings v0.4.0-rc1
|
||||
github.com/qdm12/goshutdown v0.3.0
|
||||
github.com/qdm12/gosplash v0.1.0
|
||||
github.com/qdm12/gotree v0.2.0
|
||||
github.com/qdm12/govalid v0.1.0
|
||||
github.com/qdm12/govalid v0.2.0-rc1
|
||||
github.com/qdm12/log v0.1.0
|
||||
github.com/qdm12/ss-server v0.4.0
|
||||
github.com/qdm12/ss-server v0.5.0-rc1
|
||||
github.com/qdm12/updated v0.0.0-20210603204757-205acfe6937e
|
||||
github.com/stretchr/testify v1.8.2
|
||||
github.com/stretchr/testify v1.8.4
|
||||
github.com/ulikunitz/xz v0.5.11
|
||||
github.com/vishvananda/netlink v1.2.1-beta.2
|
||||
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a
|
||||
golang.org/x/exp v0.0.0-20230519143937-03e91628a987
|
||||
golang.org/x/net v0.10.0
|
||||
golang.org/x/sys v0.8.0
|
||||
golang.org/x/text v0.9.0
|
||||
golang.org/x/net v0.12.0
|
||||
golang.org/x/sys v0.11.0
|
||||
golang.org/x/text v0.11.0
|
||||
golang.zx2c4.com/wireguard v0.0.0-20230325221338-052af4a8072b
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230215201556-9c5414ab4bde
|
||||
gopkg.in/ini.v1 v1.67.0
|
||||
inet.af/netaddr v0.0.0-20220811202034-502d2d690317
|
||||
)
|
||||
|
||||
@@ -42,8 +46,9 @@ require (
|
||||
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-20211027215823-ae77deb06f29 // indirect
|
||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20230221090011-e4bae7ad2296 // indirect
|
||||
golang.org/x/crypto v0.6.0 // indirect
|
||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20230525183740-e7c30c78aeb2 // indirect
|
||||
golang.org/x/crypto v0.11.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect
|
||||
golang.org/x/sync v0.1.0 // indirect
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
|
||||
56
go.sum
56
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.10 h1:UGVZ193UTSUASpGtg6pbDwzOd7XQP+at0Ssg1/2E4h8=
|
||||
github.com/breml/rootcerts v0.2.10/go.mod h1:24FDtzYMpqIeYC7QzaE8VPRQaFZU5TIUDlyk8qwjD88=
|
||||
github.com/breml/rootcerts v0.2.11 h1:njUAtoyZ6HUXPAPk63tGz0BEZk1/6gyfqK5fTzksHkM=
|
||||
github.com/breml/rootcerts v0.2.11/go.mod h1:S/PKh+4d1HUn4HQovEB8hPJZO6pUZYrIhmXBhsegfXw=
|
||||
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=
|
||||
@@ -37,6 +37,7 @@ 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/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
|
||||
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
||||
@@ -49,6 +50,10 @@ github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJS
|
||||
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/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I=
|
||||
github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
|
||||
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
@@ -75,6 +80,7 @@ github.com/mdlayher/socket v0.2.3/go.mod h1:bz12/FozYNH/VbvC3q7TRIK/Y6dH1kCKsXaU
|
||||
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=
|
||||
@@ -91,18 +97,20 @@ github.com/qdm12/golibs v0.0.0-20210603202746-e5494e9c2ebb/go.mod h1:15RBzkun0i8
|
||||
github.com/qdm12/golibs v0.0.0-20210723175634-a75ca7fd74c2/go.mod h1:6aRbg4Z/bTbm9JfxsGXfWKHi7zsOvPfUTK1S5HuAFKg=
|
||||
github.com/qdm12/golibs v0.0.0-20210822203818-5c568b0777b6 h1:bge5AL7cjHJMPz+5IOz5yF01q/l8No6+lIEBieA8gMg=
|
||||
github.com/qdm12/golibs v0.0.0-20210822203818-5c568b0777b6/go.mod h1:6aRbg4Z/bTbm9JfxsGXfWKHi7zsOvPfUTK1S5HuAFKg=
|
||||
github.com/qdm12/gosettings v0.4.0-rc1 h1:UYA92yyeDPbmZysIuG65yrpZVPtdIoRmtEHft/AyI38=
|
||||
github.com/qdm12/gosettings v0.4.0-rc1/go.mod h1:JRV3opOpHvnKlIA29lKQMdYw1WSMVMfHYLLHPHol5ME=
|
||||
github.com/qdm12/goshutdown v0.3.0 h1:pqBpJkdwlZlfTEx4QHtS8u8CXx6pG0fVo6S1N0MpSEM=
|
||||
github.com/qdm12/goshutdown v0.3.0/go.mod h1:EqZ46No00kCTZ5qzdd3qIzY6ayhMt24QI8Mh8LVQYmM=
|
||||
github.com/qdm12/gosplash v0.1.0 h1:Sfl+zIjFZFP7b0iqf2l5UkmEY97XBnaKkH3FNY6Gf7g=
|
||||
github.com/qdm12/gosplash v0.1.0/go.mod h1:+A3fWW4/rUeDXhY3ieBzwghKdnIPFJgD8K3qQkenJlw=
|
||||
github.com/qdm12/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/govalid v0.2.0-rc1 h1:4iYQvU4ibrASgzelsEgZX4JyKX3UTB/DcHObzQ7BXtw=
|
||||
github.com/qdm12/govalid v0.2.0-rc1/go.mod h1:/uWzVWMuS71wmbsVnlUxpQiy6EAXqm8eQ2RbyA72roQ=
|
||||
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/ss-server v0.5.0-rc1 h1:2rJEhDnUUc9AKtvyVu+CrnJwvdEjMaB1zFRQvTUlDPw=
|
||||
github.com/qdm12/ss-server v0.5.0-rc1/go.mod h1:IoFYGpVpxfIB/dMTr0PnSegdhV1gEfZLS9Tr1Qn8uRg=
|
||||
github.com/qdm12/updated v0.0.0-20210603204757-205acfe6937e h1:4q+uFLawkaQRq3yARYLsjJPZd2wYwxn4g6G/5v0xW1g=
|
||||
github.com/qdm12/updated v0.0.0-20210603204757-205acfe6937e/go.mod h1:UvJRGkZ9XL3/D7e7JiTTVLm1F3Cymd3/gFpD6frEpBo=
|
||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
|
||||
@@ -111,16 +119,14 @@ 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/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/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8=
|
||||
github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/vishvananda/netlink v1.2.1-beta.2 h1:Llsql0lnQEbHj0I1OuKyp8otXp0r3q0mPkuhwHfStVs=
|
||||
github.com/vishvananda/netlink v1.2.1-beta.2/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
|
||||
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae h1:4hwBBUfQCFe3Cym0ZtKyq7L16eZUtYKs+BaHDN6mAns=
|
||||
@@ -140,6 +146,8 @@ go4.org/unsafe/assume-no-moving-gc v0.0.0-20211027215541-db492cf91b37/go.mod h1:
|
||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760/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=
|
||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20230525183740-e7c30c78aeb2 h1:WJhcL4p+YeDxmZWg141nRm7XC8IDmhz7lk5GpadO1Sg=
|
||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20230525183740-e7c30c78aeb2/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=
|
||||
@@ -148,10 +156,10 @@ golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPh
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc=
|
||||
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
||||
golang.org/x/exp v0.0.0-20230519143937-03e91628a987 h1:3xJIFvzUFbu4ls0BTBYcgbCGhA63eAOEMxIHugyXJqA=
|
||||
golang.org/x/exp v0.0.0-20230519143937-03e91628a987/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
|
||||
golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
|
||||
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
|
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
|
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
|
||||
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=
|
||||
@@ -167,8 +175,8 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b
|
||||
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-20220923203811-8be639271d50/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50=
|
||||
golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
|
||||
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/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -197,8 +205,8 @@ golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
|
||||
golang.org/x/sys v0.11.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=
|
||||
@@ -206,9 +214,10 @@ 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.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
|
||||
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
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=
|
||||
@@ -228,6 +237,8 @@ golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230215201556-9c5414ab4bde/go.mod h1:m
|
||||
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=
|
||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98=
|
||||
gopkg.in/src-d/go-git-fixtures.v3 v3.5.0/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g=
|
||||
gopkg.in/src-d/go-git.v4 v4.13.1/go.mod h1:nx5NYcxdKxq5fpltdHnPa2Exj4Sx0EclMWZQbYDu2z8=
|
||||
@@ -238,6 +249,7 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gvisor.dev/gvisor v0.0.0-20221203005347-703fd9b7fbc0 h1:Wobr37noukisGxpKo5jAsLREcpj61RxrWYzD8uwveOY=
|
||||
gvisor.dev/gvisor v0.0.0-20221203005347-703fd9b7fbc0/go.mod h1:Dn5idtptoW1dIos9U6A2rpebLs/MtTwFacjKb8jLdQA=
|
||||
inet.af/netaddr v0.0.0-20210511181906-37180328850c/go.mod h1:z0nx+Dh+7N7CC8V5ayHtHGpZpxLQZZxkIaaz6HN65Ls=
|
||||
inet.af/netaddr v0.0.0-20220811202034-502d2d690317 h1:U2fwK6P2EqmopP/hFLTOAjWTki0qgd4GMJn5X8wOleU=
|
||||
inet.af/netaddr v0.0.0-20220811202034-502d2d690317/go.mod h1:OIezDfdzOgFhuw4HuWapWq2e9l0H9tK4F1j+ETRtF3k=
|
||||
|
||||
@@ -33,15 +33,20 @@ func addProviderFlag(flagSet *flag.FlagSet, providerToFormat map[string]*bool,
|
||||
func (c *CLI) FormatServers(args []string) error {
|
||||
var format, output string
|
||||
allProviders := providers.All()
|
||||
allProviderFlags := make([]string, len(allProviders))
|
||||
for i, provider := range allProviders {
|
||||
allProviderFlags[i] = strings.ReplaceAll(provider, " ", "-")
|
||||
}
|
||||
|
||||
providersToFormat := make(map[string]*bool, len(allProviders))
|
||||
for _, provider := range allProviders {
|
||||
for _, provider := range allProviderFlags {
|
||||
providersToFormat[provider] = new(bool)
|
||||
}
|
||||
flagSet := flag.NewFlagSet("markdown", flag.ExitOnError)
|
||||
flagSet := flag.NewFlagSet("format-servers", 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")
|
||||
titleCaser := cases.Title(language.English)
|
||||
for _, provider := range allProviders {
|
||||
for _, provider := range allProviderFlags {
|
||||
addProviderFlag(flagSet, providersToFormat, provider, titleCaser)
|
||||
}
|
||||
if err := flagSet.Parse(args); err != nil {
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"fmt"
|
||||
"net/netip"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||
"github.com/qdm12/gosettings"
|
||||
"github.com/qdm12/gotree"
|
||||
)
|
||||
|
||||
@@ -16,10 +16,14 @@ type DNS struct {
|
||||
// DoT server. It cannot be the zero value in the internal
|
||||
// state.
|
||||
ServerAddress netip.Addr
|
||||
// KeepNameserver is true if the Docker DNS server
|
||||
// found in /etc/resolv.conf should be kept.
|
||||
// Note settings this to true will go around the
|
||||
// DoT server blocking.
|
||||
// KeepNameserver is true if the existing DNS server
|
||||
// found in /etc/resolv.conf should be used
|
||||
// Note setting this to true will likely DNS traffic
|
||||
// outside the VPN tunnel since it would go through
|
||||
// the local DNS server of your Docker/Kubernetes
|
||||
// configuration, which is likely not going through the tunnel.
|
||||
// This will also disable the DNS over TLS server and the
|
||||
// `ServerAddress` field will be ignored.
|
||||
// It defaults to false and cannot be nil in the
|
||||
// internal state.
|
||||
KeepNameserver *bool
|
||||
@@ -40,7 +44,7 @@ func (d DNS) validate() (err error) {
|
||||
func (d *DNS) Copy() (copied DNS) {
|
||||
return DNS{
|
||||
ServerAddress: d.ServerAddress,
|
||||
KeepNameserver: helpers.CopyPointer(d.KeepNameserver),
|
||||
KeepNameserver: gosettings.CopyPointer(d.KeepNameserver),
|
||||
DoT: d.DoT.copy(),
|
||||
}
|
||||
}
|
||||
@@ -48,8 +52,8 @@ func (d *DNS) Copy() (copied DNS) {
|
||||
// mergeWith merges the other settings into any
|
||||
// unset field of the receiver settings object.
|
||||
func (d *DNS) mergeWith(other DNS) {
|
||||
d.ServerAddress = helpers.MergeWithIP(d.ServerAddress, other.ServerAddress)
|
||||
d.KeepNameserver = helpers.MergeWithPointer(d.KeepNameserver, other.KeepNameserver)
|
||||
d.ServerAddress = gosettings.MergeWithValidator(d.ServerAddress, other.ServerAddress)
|
||||
d.KeepNameserver = gosettings.MergeWithPointer(d.KeepNameserver, other.KeepNameserver)
|
||||
d.DoT.mergeWith(other.DoT)
|
||||
}
|
||||
|
||||
@@ -57,15 +61,15 @@ func (d *DNS) mergeWith(other DNS) {
|
||||
// settings object with any field set in the other
|
||||
// settings.
|
||||
func (d *DNS) overrideWith(other DNS) {
|
||||
d.ServerAddress = helpers.OverrideWithIP(d.ServerAddress, other.ServerAddress)
|
||||
d.KeepNameserver = helpers.OverrideWithPointer(d.KeepNameserver, other.KeepNameserver)
|
||||
d.ServerAddress = gosettings.OverrideWithValidator(d.ServerAddress, other.ServerAddress)
|
||||
d.KeepNameserver = gosettings.OverrideWithPointer(d.KeepNameserver, other.KeepNameserver)
|
||||
d.DoT.overrideWith(other.DoT)
|
||||
}
|
||||
|
||||
func (d *DNS) setDefaults() {
|
||||
localhost := netip.AddrFrom4([4]byte{127, 0, 0, 1})
|
||||
d.ServerAddress = helpers.DefaultIP(d.ServerAddress, localhost)
|
||||
d.KeepNameserver = helpers.DefaultPointer(d.KeepNameserver, false)
|
||||
d.ServerAddress = gosettings.DefaultValidator(d.ServerAddress, localhost)
|
||||
d.KeepNameserver = gosettings.DefaultPointer(d.KeepNameserver, false)
|
||||
d.DoT.setDefaults()
|
||||
}
|
||||
|
||||
@@ -75,8 +79,11 @@ func (d DNS) String() string {
|
||||
|
||||
func (d DNS) toLinesNode() (node *gotree.Node) {
|
||||
node = gotree.New("DNS settings:")
|
||||
node.Appendf("Keep existing nameserver(s): %s", gosettings.BoolToYesNo(d.KeepNameserver))
|
||||
if *d.KeepNameserver {
|
||||
return node
|
||||
}
|
||||
node.Appendf("DNS server address to use: %s", d.ServerAddress)
|
||||
node.Appendf("Keep existing nameserver(s): %s", helpers.BoolPtrToYesNo(d.KeepNameserver))
|
||||
node.AppendNode(d.DoT.toLinesNode())
|
||||
return node
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"regexp"
|
||||
|
||||
"github.com/qdm12/dns/pkg/blacklist"
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||
"github.com/qdm12/gosettings"
|
||||
"github.com/qdm12/gotree"
|
||||
)
|
||||
|
||||
@@ -23,9 +23,9 @@ type DNSBlacklist struct {
|
||||
}
|
||||
|
||||
func (b *DNSBlacklist) setDefaults() {
|
||||
b.BlockMalicious = helpers.DefaultPointer(b.BlockMalicious, true)
|
||||
b.BlockAds = helpers.DefaultPointer(b.BlockAds, false)
|
||||
b.BlockSurveillance = helpers.DefaultPointer(b.BlockSurveillance, true)
|
||||
b.BlockMalicious = gosettings.DefaultPointer(b.BlockMalicious, true)
|
||||
b.BlockAds = gosettings.DefaultPointer(b.BlockAds, false)
|
||||
b.BlockSurveillance = gosettings.DefaultPointer(b.BlockSurveillance, true)
|
||||
}
|
||||
|
||||
var hostRegex = regexp.MustCompile(`^([a-zA-Z0-9]|[a-zA-Z0-9_][a-zA-Z0-9\-_]{0,61}[a-zA-Z0-9_])(\.([a-zA-Z0-9]|[a-zA-Z0-9_][a-zA-Z0-9\-_]{0,61}[a-zA-Z0-9]))*$`) //nolint:lll
|
||||
@@ -53,34 +53,34 @@ func (b DNSBlacklist) validate() (err error) {
|
||||
|
||||
func (b DNSBlacklist) copy() (copied DNSBlacklist) {
|
||||
return DNSBlacklist{
|
||||
BlockMalicious: helpers.CopyPointer(b.BlockMalicious),
|
||||
BlockAds: helpers.CopyPointer(b.BlockAds),
|
||||
BlockSurveillance: helpers.CopyPointer(b.BlockSurveillance),
|
||||
AllowedHosts: helpers.CopySlice(b.AllowedHosts),
|
||||
AddBlockedHosts: helpers.CopySlice(b.AddBlockedHosts),
|
||||
AddBlockedIPs: helpers.CopySlice(b.AddBlockedIPs),
|
||||
AddBlockedIPPrefixes: helpers.CopySlice(b.AddBlockedIPPrefixes),
|
||||
BlockMalicious: gosettings.CopyPointer(b.BlockMalicious),
|
||||
BlockAds: gosettings.CopyPointer(b.BlockAds),
|
||||
BlockSurveillance: gosettings.CopyPointer(b.BlockSurveillance),
|
||||
AllowedHosts: gosettings.CopySlice(b.AllowedHosts),
|
||||
AddBlockedHosts: gosettings.CopySlice(b.AddBlockedHosts),
|
||||
AddBlockedIPs: gosettings.CopySlice(b.AddBlockedIPs),
|
||||
AddBlockedIPPrefixes: gosettings.CopySlice(b.AddBlockedIPPrefixes),
|
||||
}
|
||||
}
|
||||
|
||||
func (b *DNSBlacklist) mergeWith(other DNSBlacklist) {
|
||||
b.BlockMalicious = helpers.MergeWithPointer(b.BlockMalicious, other.BlockMalicious)
|
||||
b.BlockAds = helpers.MergeWithPointer(b.BlockAds, other.BlockAds)
|
||||
b.BlockSurveillance = helpers.MergeWithPointer(b.BlockSurveillance, other.BlockSurveillance)
|
||||
b.AllowedHosts = helpers.MergeSlices(b.AllowedHosts, other.AllowedHosts)
|
||||
b.AddBlockedHosts = helpers.MergeSlices(b.AddBlockedHosts, other.AddBlockedHosts)
|
||||
b.AddBlockedIPs = helpers.MergeSlices(b.AddBlockedIPs, other.AddBlockedIPs)
|
||||
b.AddBlockedIPPrefixes = helpers.MergeSlices(b.AddBlockedIPPrefixes, other.AddBlockedIPPrefixes)
|
||||
b.BlockMalicious = gosettings.MergeWithPointer(b.BlockMalicious, other.BlockMalicious)
|
||||
b.BlockAds = gosettings.MergeWithPointer(b.BlockAds, other.BlockAds)
|
||||
b.BlockSurveillance = gosettings.MergeWithPointer(b.BlockSurveillance, other.BlockSurveillance)
|
||||
b.AllowedHosts = gosettings.MergeWithSlice(b.AllowedHosts, other.AllowedHosts)
|
||||
b.AddBlockedHosts = gosettings.MergeWithSlice(b.AddBlockedHosts, other.AddBlockedHosts)
|
||||
b.AddBlockedIPs = gosettings.MergeWithSlice(b.AddBlockedIPs, other.AddBlockedIPs)
|
||||
b.AddBlockedIPPrefixes = gosettings.MergeWithSlice(b.AddBlockedIPPrefixes, other.AddBlockedIPPrefixes)
|
||||
}
|
||||
|
||||
func (b *DNSBlacklist) overrideWith(other DNSBlacklist) {
|
||||
b.BlockMalicious = helpers.OverrideWithPointer(b.BlockMalicious, other.BlockMalicious)
|
||||
b.BlockAds = helpers.OverrideWithPointer(b.BlockAds, other.BlockAds)
|
||||
b.BlockSurveillance = helpers.OverrideWithPointer(b.BlockSurveillance, other.BlockSurveillance)
|
||||
b.AllowedHosts = helpers.OverrideWithSlice(b.AllowedHosts, other.AllowedHosts)
|
||||
b.AddBlockedHosts = helpers.OverrideWithSlice(b.AddBlockedHosts, other.AddBlockedHosts)
|
||||
b.AddBlockedIPs = helpers.OverrideWithSlice(b.AddBlockedIPs, other.AddBlockedIPs)
|
||||
b.AddBlockedIPPrefixes = helpers.OverrideWithSlice(b.AddBlockedIPPrefixes, other.AddBlockedIPPrefixes)
|
||||
b.BlockMalicious = gosettings.OverrideWithPointer(b.BlockMalicious, other.BlockMalicious)
|
||||
b.BlockAds = gosettings.OverrideWithPointer(b.BlockAds, other.BlockAds)
|
||||
b.BlockSurveillance = gosettings.OverrideWithPointer(b.BlockSurveillance, other.BlockSurveillance)
|
||||
b.AllowedHosts = gosettings.OverrideWithSlice(b.AllowedHosts, other.AllowedHosts)
|
||||
b.AddBlockedHosts = gosettings.OverrideWithSlice(b.AddBlockedHosts, other.AddBlockedHosts)
|
||||
b.AddBlockedIPs = gosettings.OverrideWithSlice(b.AddBlockedIPs, other.AddBlockedIPs)
|
||||
b.AddBlockedIPPrefixes = gosettings.OverrideWithSlice(b.AddBlockedIPPrefixes, other.AddBlockedIPPrefixes)
|
||||
}
|
||||
|
||||
func (b DNSBlacklist) ToBlacklistFormat() (settings blacklist.BuilderSettings, err error) {
|
||||
@@ -102,9 +102,9 @@ func (b DNSBlacklist) String() string {
|
||||
func (b DNSBlacklist) toLinesNode() (node *gotree.Node) {
|
||||
node = gotree.New("DNS filtering settings:")
|
||||
|
||||
node.Appendf("Block malicious: %s", helpers.BoolPtrToYesNo(b.BlockMalicious))
|
||||
node.Appendf("Block ads: %s", helpers.BoolPtrToYesNo(b.BlockAds))
|
||||
node.Appendf("Block surveillance: %s", helpers.BoolPtrToYesNo(b.BlockSurveillance))
|
||||
node.Appendf("Block malicious: %s", gosettings.BoolToYesNo(b.BlockMalicious))
|
||||
node.Appendf("Block ads: %s", gosettings.BoolToYesNo(b.BlockAds))
|
||||
node.Appendf("Block surveillance: %s", gosettings.BoolToYesNo(b.BlockSurveillance))
|
||||
|
||||
if len(b.AllowedHosts) > 0 {
|
||||
allowedHostsNode := node.Appendf("Allowed hosts:")
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||
"github.com/qdm12/gosettings"
|
||||
"github.com/qdm12/gotree"
|
||||
)
|
||||
|
||||
@@ -54,8 +54,8 @@ func (d DoT) validate() (err error) {
|
||||
|
||||
func (d *DoT) copy() (copied DoT) {
|
||||
return DoT{
|
||||
Enabled: helpers.CopyPointer(d.Enabled),
|
||||
UpdatePeriod: helpers.CopyPointer(d.UpdatePeriod),
|
||||
Enabled: gosettings.CopyPointer(d.Enabled),
|
||||
UpdatePeriod: gosettings.CopyPointer(d.UpdatePeriod),
|
||||
Unbound: d.Unbound.copy(),
|
||||
Blacklist: d.Blacklist.copy(),
|
||||
}
|
||||
@@ -64,8 +64,8 @@ func (d *DoT) copy() (copied DoT) {
|
||||
// mergeWith merges the other settings into any
|
||||
// unset field of the receiver settings object.
|
||||
func (d *DoT) mergeWith(other DoT) {
|
||||
d.Enabled = helpers.MergeWithPointer(d.Enabled, other.Enabled)
|
||||
d.UpdatePeriod = helpers.MergeWithPointer(d.UpdatePeriod, other.UpdatePeriod)
|
||||
d.Enabled = gosettings.MergeWithPointer(d.Enabled, other.Enabled)
|
||||
d.UpdatePeriod = gosettings.MergeWithPointer(d.UpdatePeriod, other.UpdatePeriod)
|
||||
d.Unbound.mergeWith(other.Unbound)
|
||||
d.Blacklist.mergeWith(other.Blacklist)
|
||||
}
|
||||
@@ -74,16 +74,16 @@ func (d *DoT) mergeWith(other DoT) {
|
||||
// settings object with any field set in the other
|
||||
// settings.
|
||||
func (d *DoT) overrideWith(other DoT) {
|
||||
d.Enabled = helpers.OverrideWithPointer(d.Enabled, other.Enabled)
|
||||
d.UpdatePeriod = helpers.OverrideWithPointer(d.UpdatePeriod, other.UpdatePeriod)
|
||||
d.Enabled = gosettings.OverrideWithPointer(d.Enabled, other.Enabled)
|
||||
d.UpdatePeriod = gosettings.OverrideWithPointer(d.UpdatePeriod, other.UpdatePeriod)
|
||||
d.Unbound.overrideWith(other.Unbound)
|
||||
d.Blacklist.overrideWith(other.Blacklist)
|
||||
}
|
||||
|
||||
func (d *DoT) setDefaults() {
|
||||
d.Enabled = helpers.DefaultPointer(d.Enabled, true)
|
||||
d.Enabled = gosettings.DefaultPointer(d.Enabled, true)
|
||||
const defaultUpdatePeriod = 24 * time.Hour
|
||||
d.UpdatePeriod = helpers.DefaultPointer(d.UpdatePeriod, defaultUpdatePeriod)
|
||||
d.UpdatePeriod = gosettings.DefaultPointer(d.UpdatePeriod, defaultUpdatePeriod)
|
||||
d.Unbound.setDefaults()
|
||||
d.Blacklist.setDefaults()
|
||||
}
|
||||
@@ -95,7 +95,7 @@ func (d DoT) String() string {
|
||||
func (d DoT) toLinesNode() (node *gotree.Node) {
|
||||
node = gotree.New("DNS over TLS settings:")
|
||||
|
||||
node.Appendf("Enabled: %s", helpers.BoolPtrToYesNo(d.Enabled))
|
||||
node.Appendf("Enabled: %s", gosettings.BoolToYesNo(d.Enabled))
|
||||
if !*d.Enabled {
|
||||
return node
|
||||
}
|
||||
|
||||
@@ -34,6 +34,8 @@ var (
|
||||
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")
|
||||
ErrWireguardAllowedIPNotSet = errors.New("allowed IP is not set")
|
||||
ErrWireguardAllowedIPsNotSet = errors.New("allowed IPs is not set")
|
||||
ErrWireguardEndpointIPNotSet = errors.New("endpoint IP is not set")
|
||||
ErrWireguardEndpointPortNotAllowed = errors.New("endpoint port is not allowed")
|
||||
ErrWireguardEndpointPortNotSet = errors.New("endpoint port is not set")
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"fmt"
|
||||
"net/netip"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||
"github.com/qdm12/gosettings"
|
||||
"github.com/qdm12/gotree"
|
||||
)
|
||||
|
||||
@@ -40,11 +40,11 @@ func hasZeroPort(ports []uint16) (has bool) {
|
||||
|
||||
func (f *Firewall) copy() (copied Firewall) {
|
||||
return Firewall{
|
||||
VPNInputPorts: helpers.CopySlice(f.VPNInputPorts),
|
||||
InputPorts: helpers.CopySlice(f.InputPorts),
|
||||
OutboundSubnets: helpers.CopySlice(f.OutboundSubnets),
|
||||
Enabled: helpers.CopyPointer(f.Enabled),
|
||||
Debug: helpers.CopyPointer(f.Debug),
|
||||
VPNInputPorts: gosettings.CopySlice(f.VPNInputPorts),
|
||||
InputPorts: gosettings.CopySlice(f.InputPorts),
|
||||
OutboundSubnets: gosettings.CopySlice(f.OutboundSubnets),
|
||||
Enabled: gosettings.CopyPointer(f.Enabled),
|
||||
Debug: gosettings.CopyPointer(f.Debug),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,27 +53,27 @@ func (f *Firewall) copy() (copied Firewall) {
|
||||
// It merges values of slices together, even if they
|
||||
// are set in the receiver settings.
|
||||
func (f *Firewall) mergeWith(other Firewall) {
|
||||
f.VPNInputPorts = helpers.MergeSlices(f.VPNInputPorts, other.VPNInputPorts)
|
||||
f.InputPorts = helpers.MergeSlices(f.InputPorts, other.InputPorts)
|
||||
f.OutboundSubnets = helpers.MergeSlices(f.OutboundSubnets, other.OutboundSubnets)
|
||||
f.Enabled = helpers.MergeWithPointer(f.Enabled, other.Enabled)
|
||||
f.Debug = helpers.MergeWithPointer(f.Debug, other.Debug)
|
||||
f.VPNInputPorts = gosettings.MergeWithSlice(f.VPNInputPorts, other.VPNInputPorts)
|
||||
f.InputPorts = gosettings.MergeWithSlice(f.InputPorts, other.InputPorts)
|
||||
f.OutboundSubnets = gosettings.MergeWithSlice(f.OutboundSubnets, other.OutboundSubnets)
|
||||
f.Enabled = gosettings.MergeWithPointer(f.Enabled, other.Enabled)
|
||||
f.Debug = gosettings.MergeWithPointer(f.Debug, other.Debug)
|
||||
}
|
||||
|
||||
// overrideWith overrides fields of the receiver
|
||||
// settings object with any field set in the other
|
||||
// settings.
|
||||
func (f *Firewall) overrideWith(other Firewall) {
|
||||
f.VPNInputPorts = helpers.OverrideWithSlice(f.VPNInputPorts, other.VPNInputPorts)
|
||||
f.InputPorts = helpers.OverrideWithSlice(f.InputPorts, other.InputPorts)
|
||||
f.OutboundSubnets = helpers.OverrideWithSlice(f.OutboundSubnets, other.OutboundSubnets)
|
||||
f.Enabled = helpers.OverrideWithPointer(f.Enabled, other.Enabled)
|
||||
f.Debug = helpers.OverrideWithPointer(f.Debug, other.Debug)
|
||||
f.VPNInputPorts = gosettings.OverrideWithSlice(f.VPNInputPorts, other.VPNInputPorts)
|
||||
f.InputPorts = gosettings.OverrideWithSlice(f.InputPorts, other.InputPorts)
|
||||
f.OutboundSubnets = gosettings.OverrideWithSlice(f.OutboundSubnets, other.OutboundSubnets)
|
||||
f.Enabled = gosettings.OverrideWithPointer(f.Enabled, other.Enabled)
|
||||
f.Debug = gosettings.OverrideWithPointer(f.Debug, other.Debug)
|
||||
}
|
||||
|
||||
func (f *Firewall) setDefaults() {
|
||||
f.Enabled = helpers.DefaultPointer(f.Enabled, true)
|
||||
f.Debug = helpers.DefaultPointer(f.Debug, false)
|
||||
f.Enabled = gosettings.DefaultPointer(f.Enabled, true)
|
||||
f.Debug = gosettings.DefaultPointer(f.Debug, false)
|
||||
}
|
||||
|
||||
func (f Firewall) String() string {
|
||||
@@ -83,7 +83,7 @@ func (f Firewall) String() string {
|
||||
func (f Firewall) toLinesNode() (node *gotree.Node) {
|
||||
node = gotree.New("Firewall settings:")
|
||||
|
||||
node.Appendf("Enabled: %s", helpers.BoolPtrToYesNo(f.Enabled))
|
||||
node.Appendf("Enabled: %s", gosettings.BoolToYesNo(f.Enabled))
|
||||
if !*f.Enabled {
|
||||
return node
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||
"github.com/qdm12/gosettings"
|
||||
"github.com/qdm12/gotree"
|
||||
"github.com/qdm12/govalid/address"
|
||||
)
|
||||
@@ -37,7 +37,7 @@ type Health struct {
|
||||
|
||||
func (h Health) Validate() (err error) {
|
||||
uid := os.Getuid()
|
||||
_, err = address.Validate(h.ServerAddress,
|
||||
err = address.Validate(h.ServerAddress,
|
||||
address.OptionListening(uid))
|
||||
if err != nil {
|
||||
return fmt.Errorf("server listening address is not valid: %w", err)
|
||||
@@ -65,11 +65,11 @@ func (h *Health) copy() (copied Health) {
|
||||
// MergeWith merges the other settings into any
|
||||
// unset field of the receiver settings object.
|
||||
func (h *Health) MergeWith(other Health) {
|
||||
h.ServerAddress = helpers.MergeWithString(h.ServerAddress, other.ServerAddress)
|
||||
h.ReadHeaderTimeout = helpers.MergeWithNumber(h.ReadHeaderTimeout, other.ReadHeaderTimeout)
|
||||
h.ReadTimeout = helpers.MergeWithNumber(h.ReadTimeout, other.ReadTimeout)
|
||||
h.TargetAddress = helpers.MergeWithString(h.TargetAddress, other.TargetAddress)
|
||||
h.SuccessWait = helpers.MergeWithNumber(h.SuccessWait, other.SuccessWait)
|
||||
h.ServerAddress = gosettings.MergeWithString(h.ServerAddress, other.ServerAddress)
|
||||
h.ReadHeaderTimeout = gosettings.MergeWithNumber(h.ReadHeaderTimeout, other.ReadHeaderTimeout)
|
||||
h.ReadTimeout = gosettings.MergeWithNumber(h.ReadTimeout, other.ReadTimeout)
|
||||
h.TargetAddress = gosettings.MergeWithString(h.TargetAddress, other.TargetAddress)
|
||||
h.SuccessWait = gosettings.MergeWithNumber(h.SuccessWait, other.SuccessWait)
|
||||
h.VPN.mergeWith(other.VPN)
|
||||
}
|
||||
|
||||
@@ -77,23 +77,23 @@ func (h *Health) MergeWith(other Health) {
|
||||
// settings object with any field set in the other
|
||||
// settings.
|
||||
func (h *Health) OverrideWith(other Health) {
|
||||
h.ServerAddress = helpers.OverrideWithString(h.ServerAddress, other.ServerAddress)
|
||||
h.ReadHeaderTimeout = helpers.OverrideWithNumber(h.ReadHeaderTimeout, other.ReadHeaderTimeout)
|
||||
h.ReadTimeout = helpers.OverrideWithNumber(h.ReadTimeout, other.ReadTimeout)
|
||||
h.TargetAddress = helpers.OverrideWithString(h.TargetAddress, other.TargetAddress)
|
||||
h.SuccessWait = helpers.OverrideWithNumber(h.SuccessWait, other.SuccessWait)
|
||||
h.ServerAddress = gosettings.OverrideWithString(h.ServerAddress, other.ServerAddress)
|
||||
h.ReadHeaderTimeout = gosettings.OverrideWithNumber(h.ReadHeaderTimeout, other.ReadHeaderTimeout)
|
||||
h.ReadTimeout = gosettings.OverrideWithNumber(h.ReadTimeout, other.ReadTimeout)
|
||||
h.TargetAddress = gosettings.OverrideWithString(h.TargetAddress, other.TargetAddress)
|
||||
h.SuccessWait = gosettings.OverrideWithNumber(h.SuccessWait, other.SuccessWait)
|
||||
h.VPN.overrideWith(other.VPN)
|
||||
}
|
||||
|
||||
func (h *Health) SetDefaults() {
|
||||
h.ServerAddress = helpers.DefaultString(h.ServerAddress, "127.0.0.1:9999")
|
||||
h.ServerAddress = gosettings.DefaultString(h.ServerAddress, "127.0.0.1:9999")
|
||||
const defaultReadHeaderTimeout = 100 * time.Millisecond
|
||||
h.ReadHeaderTimeout = helpers.DefaultNumber(h.ReadHeaderTimeout, defaultReadHeaderTimeout)
|
||||
h.ReadHeaderTimeout = gosettings.DefaultNumber(h.ReadHeaderTimeout, defaultReadHeaderTimeout)
|
||||
const defaultReadTimeout = 500 * time.Millisecond
|
||||
h.ReadTimeout = helpers.DefaultNumber(h.ReadTimeout, defaultReadTimeout)
|
||||
h.TargetAddress = helpers.DefaultString(h.TargetAddress, "cloudflare.com:443")
|
||||
h.ReadTimeout = gosettings.DefaultNumber(h.ReadTimeout, defaultReadTimeout)
|
||||
h.TargetAddress = gosettings.DefaultString(h.TargetAddress, "cloudflare.com:443")
|
||||
const defaultSuccessWait = 5 * time.Second
|
||||
h.SuccessWait = helpers.DefaultNumber(h.SuccessWait, defaultSuccessWait)
|
||||
h.SuccessWait = gosettings.DefaultNumber(h.SuccessWait, defaultSuccessWait)
|
||||
h.VPN.setDefaults()
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ package settings
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||
"github.com/qdm12/gosettings"
|
||||
"github.com/qdm12/gotree"
|
||||
)
|
||||
|
||||
@@ -27,31 +27,31 @@ func (h HealthyWait) validate() (err error) {
|
||||
// unset field of the receiver settings object.
|
||||
func (h *HealthyWait) copy() (copied HealthyWait) {
|
||||
return HealthyWait{
|
||||
Initial: helpers.CopyPointer(h.Initial),
|
||||
Addition: helpers.CopyPointer(h.Addition),
|
||||
Initial: gosettings.CopyPointer(h.Initial),
|
||||
Addition: gosettings.CopyPointer(h.Addition),
|
||||
}
|
||||
}
|
||||
|
||||
// mergeWith merges the other settings into any
|
||||
// unset field of the receiver settings object.
|
||||
func (h *HealthyWait) mergeWith(other HealthyWait) {
|
||||
h.Initial = helpers.MergeWithPointer(h.Initial, other.Initial)
|
||||
h.Addition = helpers.MergeWithPointer(h.Addition, other.Addition)
|
||||
h.Initial = gosettings.MergeWithPointer(h.Initial, other.Initial)
|
||||
h.Addition = gosettings.MergeWithPointer(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.OverrideWithPointer(h.Initial, other.Initial)
|
||||
h.Addition = helpers.OverrideWithPointer(h.Addition, other.Addition)
|
||||
h.Initial = gosettings.OverrideWithPointer(h.Initial, other.Initial)
|
||||
h.Addition = gosettings.OverrideWithPointer(h.Addition, other.Addition)
|
||||
}
|
||||
|
||||
func (h *HealthyWait) setDefaults() {
|
||||
const initialDurationDefault = 6 * time.Second
|
||||
const additionDurationDefault = 5 * time.Second
|
||||
h.Initial = helpers.DefaultPointer(h.Initial, initialDurationDefault)
|
||||
h.Addition = helpers.DefaultPointer(h.Addition, additionDurationDefault)
|
||||
h.Initial = gosettings.DefaultPointer(h.Initial, initialDurationDefault)
|
||||
h.Addition = gosettings.DefaultPointer(h.Addition, additionDurationDefault)
|
||||
}
|
||||
|
||||
func (h HealthyWait) String() string {
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func IsOneOf(value string, choices ...string) (ok bool) {
|
||||
func IsOneOf[T comparable](value T, choices ...T) (ok bool) {
|
||||
for _, choice := range choices {
|
||||
if value == choice {
|
||||
return true
|
||||
@@ -14,39 +8,3 @@ func IsOneOf(value string, choices ...string) (ok bool) {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
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 fmt.Errorf("%w", ErrNoChoice)
|
||||
}
|
||||
|
||||
set := make(map[string]struct{}, len(choices))
|
||||
for _, choice := range choices {
|
||||
choice = strings.ToLower(choice)
|
||||
set[choice] = struct{}{}
|
||||
}
|
||||
|
||||
for _, value := range values {
|
||||
_, ok := set[value]
|
||||
if !ok {
|
||||
return fmt.Errorf("%w: value %q, choices available are %s",
|
||||
ErrValueNotOneOf, value, strings.Join(choices, ", "))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func Uint16IsOneOf(port uint16, choices []uint16) (ok bool) {
|
||||
for _, choice := range choices {
|
||||
if port == choice {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
func CopyPointer[T any](original *T) (copied *T) {
|
||||
if original == nil {
|
||||
return nil
|
||||
}
|
||||
copied = new(T)
|
||||
*copied = *original
|
||||
return copied
|
||||
}
|
||||
|
||||
func CopySlice[T string | uint16 | netip.Addr | netip.Prefix](original []T) (copied []T) {
|
||||
return slices.Clone(original)
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
)
|
||||
|
||||
func DefaultPointer[T any](existing *T, defaultValue T) (
|
||||
result *T) {
|
||||
if existing != nil {
|
||||
return existing
|
||||
}
|
||||
result = new(T)
|
||||
*result = defaultValue
|
||||
return result
|
||||
}
|
||||
|
||||
func DefaultString(existing string, defaultValue string) (
|
||||
result string) {
|
||||
if existing != "" {
|
||||
return existing
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
func DefaultNumber[T Number](existing T, defaultValue T) ( //nolint:ireturn
|
||||
result T) {
|
||||
if existing != 0 {
|
||||
return existing
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
func DefaultIP(existing netip.Addr, defaultValue netip.Addr) (
|
||||
result netip.Addr) {
|
||||
if existing.IsValid() {
|
||||
return existing
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrFileDoesNotExist = errors.New("file does not exist")
|
||||
ErrFileRead = errors.New("cannot read file")
|
||||
ErrFileClose = errors.New("cannot close file")
|
||||
)
|
||||
|
||||
func FileExists(path string) (err error) {
|
||||
path = filepath.Clean(path)
|
||||
|
||||
f, err := os.Open(path)
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
return fmt.Errorf("%w: %s", ErrFileDoesNotExist, path)
|
||||
} else if err != nil {
|
||||
return fmt.Errorf("%w: %s", ErrFileRead, err)
|
||||
}
|
||||
|
||||
if err := f.Close(); err != nil {
|
||||
return fmt.Errorf("%w: %s", ErrFileClose, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
package helpers
|
||||
|
||||
import "time"
|
||||
|
||||
type Number interface {
|
||||
uint8 | uint16 | uint32 | uint64 | uint |
|
||||
int8 | int16 | int32 | int64 | int |
|
||||
float32 | float64 |
|
||||
time.Duration
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/netip"
|
||||
)
|
||||
|
||||
func MergeWithPointer[T any](existing, other *T) (result *T) {
|
||||
if existing != nil {
|
||||
return existing
|
||||
} else if other == nil {
|
||||
return nil
|
||||
}
|
||||
result = new(T)
|
||||
*result = *other
|
||||
return result
|
||||
}
|
||||
|
||||
func MergeWithString(existing, other string) (result string) {
|
||||
if existing != "" {
|
||||
return existing
|
||||
}
|
||||
return other
|
||||
}
|
||||
|
||||
func MergeWithNumber[T Number](existing, other T) (result T) { //nolint:ireturn
|
||||
if existing != 0 {
|
||||
return existing
|
||||
}
|
||||
return other
|
||||
}
|
||||
|
||||
func MergeWithIP(existing, other netip.Addr) (result netip.Addr) {
|
||||
if existing.IsValid() {
|
||||
return existing
|
||||
}
|
||||
return other
|
||||
}
|
||||
|
||||
func MergeWithHTTPHandler(existing, other http.Handler) (result http.Handler) {
|
||||
if existing != nil {
|
||||
return existing
|
||||
}
|
||||
return other
|
||||
}
|
||||
|
||||
func MergeSlices[T comparable](a, b []T) (result []T) {
|
||||
if a == nil && b == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
seen := make(map[T]struct{}, len(a)+len(b))
|
||||
result = make([]T, 0, len(a)+len(b))
|
||||
for _, s := range a {
|
||||
if _, ok := seen[s]; ok {
|
||||
continue // duplicate
|
||||
}
|
||||
result = append(result, s)
|
||||
seen[s] = struct{}{}
|
||||
}
|
||||
for _, s := range b {
|
||||
if _, ok := seen[s]; ok {
|
||||
continue // duplicate
|
||||
}
|
||||
result = append(result, s)
|
||||
seen[s] = struct{}{}
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func ChoicesOrString(choices []string) string {
|
||||
return strings.Join(
|
||||
choices[:len(choices)-1], ", ") +
|
||||
" or " + choices[len(choices)-1]
|
||||
}
|
||||
|
||||
func PortChoicesOrString(ports []uint16) (s string) {
|
||||
switch len(ports) {
|
||||
case 0:
|
||||
return "there is no allowed port"
|
||||
case 1:
|
||||
return "allowed port is " + fmt.Sprint(ports[0])
|
||||
}
|
||||
|
||||
s = "allowed ports are "
|
||||
portStrings := make([]string, len(ports))
|
||||
for i := range ports {
|
||||
portStrings[i] = fmt.Sprint(ports[i])
|
||||
}
|
||||
s += ChoicesOrString(portStrings)
|
||||
return s
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package helpers
|
||||
|
||||
func ObfuscateWireguardKey(fullKey string) (obfuscatedKey string) {
|
||||
const minKeyLength = 10
|
||||
if len(fullKey) < minKeyLength {
|
||||
return "(too short)"
|
||||
}
|
||||
|
||||
lastIndex := len(fullKey) - 1
|
||||
return fullKey[0:2] + "..." + fullKey[lastIndex-2:]
|
||||
}
|
||||
|
||||
func ObfuscatePassword(password string) (obfuscatedPassword string) {
|
||||
if password != "" {
|
||||
return "[set]"
|
||||
}
|
||||
return "[not set]"
|
||||
}
|
||||
|
||||
func ObfuscateData(data string) (obfuscated string) {
|
||||
if data != "" {
|
||||
return "[set]"
|
||||
}
|
||||
return "[not set]"
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/netip"
|
||||
)
|
||||
|
||||
func OverrideWithPointer[T any](existing, other *T) (result *T) {
|
||||
if other == nil {
|
||||
return existing
|
||||
}
|
||||
result = new(T)
|
||||
*result = *other
|
||||
return result
|
||||
}
|
||||
|
||||
func OverrideWithString(existing, other string) (result string) {
|
||||
if other == "" {
|
||||
return existing
|
||||
}
|
||||
return other
|
||||
}
|
||||
|
||||
func OverrideWithNumber[T Number](existing, other T) (result T) { //nolint:ireturn
|
||||
if other == 0 {
|
||||
return existing
|
||||
}
|
||||
return other
|
||||
}
|
||||
|
||||
func OverrideWithIP(existing, other netip.Addr) (result netip.Addr) {
|
||||
if !other.IsValid() {
|
||||
return existing
|
||||
}
|
||||
return other
|
||||
}
|
||||
|
||||
func OverrideWithHTTPHandler(existing, other http.Handler) (result http.Handler) {
|
||||
if other != nil {
|
||||
return other
|
||||
}
|
||||
return existing
|
||||
}
|
||||
|
||||
func OverrideWithSlice[T any](existing, other []T) (result []T) {
|
||||
if other == nil {
|
||||
return existing
|
||||
}
|
||||
result = make([]T, len(other))
|
||||
copy(result, other)
|
||||
return result
|
||||
}
|
||||
@@ -1,12 +1,5 @@
|
||||
package helpers
|
||||
|
||||
func BoolPtrToYesNo(b *bool) string {
|
||||
if *b {
|
||||
return "yes"
|
||||
}
|
||||
return "no"
|
||||
}
|
||||
|
||||
func TCPPtrToString(tcp *bool) string {
|
||||
if *tcp {
|
||||
return "TCP"
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||
"github.com/qdm12/gosettings"
|
||||
"github.com/qdm12/gotree"
|
||||
"github.com/qdm12/govalid/address"
|
||||
)
|
||||
@@ -46,7 +46,7 @@ func (h HTTPProxy) validate() (err error) {
|
||||
// Do not validate user and password
|
||||
|
||||
uid := os.Getuid()
|
||||
_, err = address.Validate(h.ListeningAddress, address.OptionListening(uid))
|
||||
err = address.Validate(h.ListeningAddress, address.OptionListening(uid))
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %s", ErrServerAddressNotValid, h.ListeningAddress)
|
||||
}
|
||||
@@ -56,12 +56,12 @@ func (h HTTPProxy) validate() (err error) {
|
||||
|
||||
func (h *HTTPProxy) copy() (copied HTTPProxy) {
|
||||
return HTTPProxy{
|
||||
User: helpers.CopyPointer(h.User),
|
||||
Password: helpers.CopyPointer(h.Password),
|
||||
User: gosettings.CopyPointer(h.User),
|
||||
Password: gosettings.CopyPointer(h.Password),
|
||||
ListeningAddress: h.ListeningAddress,
|
||||
Enabled: helpers.CopyPointer(h.Enabled),
|
||||
Stealth: helpers.CopyPointer(h.Stealth),
|
||||
Log: helpers.CopyPointer(h.Log),
|
||||
Enabled: gosettings.CopyPointer(h.Enabled),
|
||||
Stealth: gosettings.CopyPointer(h.Stealth),
|
||||
Log: gosettings.CopyPointer(h.Log),
|
||||
ReadHeaderTimeout: h.ReadHeaderTimeout,
|
||||
ReadTimeout: h.ReadTimeout,
|
||||
}
|
||||
@@ -70,41 +70,41 @@ func (h *HTTPProxy) copy() (copied HTTPProxy) {
|
||||
// mergeWith merges the other settings into any
|
||||
// unset field of the receiver settings object.
|
||||
func (h *HTTPProxy) mergeWith(other HTTPProxy) {
|
||||
h.User = helpers.MergeWithPointer(h.User, other.User)
|
||||
h.Password = helpers.MergeWithPointer(h.Password, other.Password)
|
||||
h.ListeningAddress = helpers.MergeWithString(h.ListeningAddress, other.ListeningAddress)
|
||||
h.Enabled = helpers.MergeWithPointer(h.Enabled, other.Enabled)
|
||||
h.Stealth = helpers.MergeWithPointer(h.Stealth, other.Stealth)
|
||||
h.Log = helpers.MergeWithPointer(h.Log, other.Log)
|
||||
h.ReadHeaderTimeout = helpers.MergeWithNumber(h.ReadHeaderTimeout, other.ReadHeaderTimeout)
|
||||
h.ReadTimeout = helpers.MergeWithNumber(h.ReadTimeout, other.ReadTimeout)
|
||||
h.User = gosettings.MergeWithPointer(h.User, other.User)
|
||||
h.Password = gosettings.MergeWithPointer(h.Password, other.Password)
|
||||
h.ListeningAddress = gosettings.MergeWithString(h.ListeningAddress, other.ListeningAddress)
|
||||
h.Enabled = gosettings.MergeWithPointer(h.Enabled, other.Enabled)
|
||||
h.Stealth = gosettings.MergeWithPointer(h.Stealth, other.Stealth)
|
||||
h.Log = gosettings.MergeWithPointer(h.Log, other.Log)
|
||||
h.ReadHeaderTimeout = gosettings.MergeWithNumber(h.ReadHeaderTimeout, other.ReadHeaderTimeout)
|
||||
h.ReadTimeout = gosettings.MergeWithNumber(h.ReadTimeout, other.ReadTimeout)
|
||||
}
|
||||
|
||||
// overrideWith overrides fields of the receiver
|
||||
// settings object with any field set in the other
|
||||
// settings.
|
||||
func (h *HTTPProxy) overrideWith(other HTTPProxy) {
|
||||
h.User = helpers.OverrideWithPointer(h.User, other.User)
|
||||
h.Password = helpers.OverrideWithPointer(h.Password, other.Password)
|
||||
h.ListeningAddress = helpers.OverrideWithString(h.ListeningAddress, other.ListeningAddress)
|
||||
h.Enabled = helpers.OverrideWithPointer(h.Enabled, other.Enabled)
|
||||
h.Stealth = helpers.OverrideWithPointer(h.Stealth, other.Stealth)
|
||||
h.Log = helpers.OverrideWithPointer(h.Log, other.Log)
|
||||
h.ReadHeaderTimeout = helpers.OverrideWithNumber(h.ReadHeaderTimeout, other.ReadHeaderTimeout)
|
||||
h.ReadTimeout = helpers.OverrideWithNumber(h.ReadTimeout, other.ReadTimeout)
|
||||
h.User = gosettings.OverrideWithPointer(h.User, other.User)
|
||||
h.Password = gosettings.OverrideWithPointer(h.Password, other.Password)
|
||||
h.ListeningAddress = gosettings.OverrideWithString(h.ListeningAddress, other.ListeningAddress)
|
||||
h.Enabled = gosettings.OverrideWithPointer(h.Enabled, other.Enabled)
|
||||
h.Stealth = gosettings.OverrideWithPointer(h.Stealth, other.Stealth)
|
||||
h.Log = gosettings.OverrideWithPointer(h.Log, other.Log)
|
||||
h.ReadHeaderTimeout = gosettings.OverrideWithNumber(h.ReadHeaderTimeout, other.ReadHeaderTimeout)
|
||||
h.ReadTimeout = gosettings.OverrideWithNumber(h.ReadTimeout, other.ReadTimeout)
|
||||
}
|
||||
|
||||
func (h *HTTPProxy) setDefaults() {
|
||||
h.User = helpers.DefaultPointer(h.User, "")
|
||||
h.Password = helpers.DefaultPointer(h.Password, "")
|
||||
h.ListeningAddress = helpers.DefaultString(h.ListeningAddress, ":8888")
|
||||
h.Enabled = helpers.DefaultPointer(h.Enabled, false)
|
||||
h.Stealth = helpers.DefaultPointer(h.Stealth, false)
|
||||
h.Log = helpers.DefaultPointer(h.Log, false)
|
||||
h.User = gosettings.DefaultPointer(h.User, "")
|
||||
h.Password = gosettings.DefaultPointer(h.Password, "")
|
||||
h.ListeningAddress = gosettings.DefaultString(h.ListeningAddress, ":8888")
|
||||
h.Enabled = gosettings.DefaultPointer(h.Enabled, false)
|
||||
h.Stealth = gosettings.DefaultPointer(h.Stealth, false)
|
||||
h.Log = gosettings.DefaultPointer(h.Log, false)
|
||||
const defaultReadHeaderTimeout = time.Second
|
||||
h.ReadHeaderTimeout = helpers.DefaultNumber(h.ReadHeaderTimeout, defaultReadHeaderTimeout)
|
||||
h.ReadHeaderTimeout = gosettings.DefaultNumber(h.ReadHeaderTimeout, defaultReadHeaderTimeout)
|
||||
const defaultReadTimeout = 3 * time.Second
|
||||
h.ReadTimeout = helpers.DefaultNumber(h.ReadTimeout, defaultReadTimeout)
|
||||
h.ReadTimeout = gosettings.DefaultNumber(h.ReadTimeout, defaultReadTimeout)
|
||||
}
|
||||
|
||||
func (h HTTPProxy) String() string {
|
||||
@@ -113,16 +113,16 @@ func (h HTTPProxy) String() string {
|
||||
|
||||
func (h HTTPProxy) toLinesNode() (node *gotree.Node) {
|
||||
node = gotree.New("HTTP proxy settings:")
|
||||
node.Appendf("Enabled: %s", helpers.BoolPtrToYesNo(h.Enabled))
|
||||
node.Appendf("Enabled: %s", gosettings.BoolToYesNo(h.Enabled))
|
||||
if !*h.Enabled {
|
||||
return node
|
||||
}
|
||||
|
||||
node.Appendf("Listening address: %s", h.ListeningAddress)
|
||||
node.Appendf("User: %s", *h.User)
|
||||
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("Password: %s", gosettings.ObfuscateKey(*h.Password))
|
||||
node.Appendf("Stealth mode: %s", gosettings.BoolToYesNo(h.Stealth))
|
||||
node.Appendf("Log: %s", gosettings.BoolToYesNo(h.Log))
|
||||
node.Appendf("Read header timeout: %s", h.ReadHeaderTimeout)
|
||||
node.Appendf("Read timeout: %s", h.ReadTimeout)
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package settings
|
||||
|
||||
import (
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||
"github.com/qdm12/gosettings"
|
||||
"github.com/qdm12/gotree"
|
||||
"github.com/qdm12/log"
|
||||
)
|
||||
@@ -19,25 +19,25 @@ func (l Log) validate() (err error) {
|
||||
|
||||
func (l *Log) copy() (copied Log) {
|
||||
return Log{
|
||||
Level: helpers.CopyPointer(l.Level),
|
||||
Level: gosettings.CopyPointer(l.Level),
|
||||
}
|
||||
}
|
||||
|
||||
// mergeWith merges the other settings into any
|
||||
// unset field of the receiver settings object.
|
||||
func (l *Log) mergeWith(other Log) {
|
||||
l.Level = helpers.MergeWithPointer(l.Level, other.Level)
|
||||
l.Level = gosettings.MergeWithPointer(l.Level, other.Level)
|
||||
}
|
||||
|
||||
// overrideWith overrides fields of the receiver
|
||||
// settings object with any field set in the other
|
||||
// settings.
|
||||
func (l *Log) overrideWith(other Log) {
|
||||
l.Level = helpers.OverrideWithPointer(l.Level, other.Level)
|
||||
l.Level = gosettings.OverrideWithPointer(l.Level, other.Level)
|
||||
}
|
||||
|
||||
func (l *Log) setDefaults() {
|
||||
l.Level = helpers.DefaultPointer(l.Level, log.LevelInfo)
|
||||
l.Level = gosettings.DefaultPointer(l.Level, log.LevelInfo)
|
||||
}
|
||||
|
||||
func (l Log) String() string {
|
||||
|
||||
42
internal/configuration/settings/nordvpn_retro.go
Normal file
42
internal/configuration/settings/nordvpn_retro.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package settings
|
||||
|
||||
// Retro-compatibility because SERVER_REGIONS changed to SERVER_COUNTRIES
|
||||
// and SERVER_REGIONS is now the continent field for servers.
|
||||
// TODO v4 remove.
|
||||
func nordvpnRetroRegion(selection ServerSelection, validRegions, validCountries []string) (
|
||||
updatedSelection ServerSelection) {
|
||||
validRegionsMap := stringSliceToMap(validRegions)
|
||||
validCountriesMap := stringSliceToMap(validCountries)
|
||||
|
||||
updatedSelection = selection.copy()
|
||||
updatedSelection.Regions = make([]string, 0, len(selection.Regions))
|
||||
for _, region := range selection.Regions {
|
||||
_, isValid := validRegionsMap[region]
|
||||
if isValid {
|
||||
updatedSelection.Regions = append(updatedSelection.Regions, region)
|
||||
continue
|
||||
}
|
||||
|
||||
_, isValid = validCountriesMap[region]
|
||||
if !isValid {
|
||||
// Region is not valid for the country or region
|
||||
// just leave it to the validation to fail it later
|
||||
continue
|
||||
}
|
||||
|
||||
// Region is not valid for a region, but is a valid country
|
||||
// Handle retro-compatibility and transfer the value to the
|
||||
// country field.
|
||||
updatedSelection.Countries = append(updatedSelection.Countries, region)
|
||||
}
|
||||
|
||||
return updatedSelection
|
||||
}
|
||||
|
||||
func stringSliceToMap(slice []string) (m map[string]struct{}) {
|
||||
m = make(map[string]struct{}, len(slice))
|
||||
for _, s := range slice {
|
||||
m[s] = struct{}{}
|
||||
}
|
||||
return m
|
||||
}
|
||||
@@ -4,94 +4,93 @@ import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"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/openvpn/extract"
|
||||
"github.com/qdm12/gluetun/internal/provider/privateinternetaccess/presets"
|
||||
"github.com/qdm12/gosettings"
|
||||
"github.com/qdm12/gosettings/validate"
|
||||
"github.com/qdm12/gotree"
|
||||
)
|
||||
|
||||
// OpenVPN contains settings to configure the OpenVPN client.
|
||||
type OpenVPN struct {
|
||||
// Version is the OpenVPN version to run.
|
||||
// It can only be "2.4" or "2.5".
|
||||
Version string
|
||||
// It can only be "2.5" or "2.6".
|
||||
Version string `json:"version"`
|
||||
// User is the OpenVPN authentication username.
|
||||
// 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
|
||||
User *string `json:"user"`
|
||||
// Password is the OpenVPN authentication password.
|
||||
// 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
|
||||
Password *string `json:"password"`
|
||||
// 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.
|
||||
ConfFile *string
|
||||
ConfFile *string `json:"config_file_path"`
|
||||
// Ciphers is a list of ciphers to use for OpenVPN,
|
||||
// different from the ones specified by the VPN
|
||||
// service provider configuration files.
|
||||
Ciphers []string
|
||||
Ciphers []string `json:"ciphers"`
|
||||
// Auth is an auth algorithm to use in OpenVPN instead
|
||||
// of the one specified by the VPN service provider.
|
||||
// It cannot be nil in the internal state.
|
||||
// It is ignored if it is set to the empty string.
|
||||
Auth *string
|
||||
Auth *string `json:"auth"`
|
||||
// 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.
|
||||
Cert *string
|
||||
Cert *string `json:"cert"`
|
||||
// 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.
|
||||
Key *string
|
||||
Key *string `json:"key"`
|
||||
// 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
|
||||
EncryptedKey *string `json:"encrypted_key"`
|
||||
// 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
|
||||
KeyPassphrase *string `json:"key_passphrase"`
|
||||
// PIAEncPreset is the encryption preset for
|
||||
// Private Internet Access. It can be set to an
|
||||
// empty string for other providers.
|
||||
PIAEncPreset *string
|
||||
PIAEncPreset *string `json:"pia_encryption_preset"`
|
||||
// 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.
|
||||
MSSFix *uint16
|
||||
MSSFix *uint16 `json:"mssfix"`
|
||||
// Interface is the OpenVPN device interface name.
|
||||
// It cannot be an empty string in the internal state.
|
||||
Interface string
|
||||
Interface string `json:"interface"`
|
||||
// ProcessUser is the OpenVPN process OS username
|
||||
// to use. It cannot be empty in the internal state.
|
||||
// It defaults to 'root'.
|
||||
ProcessUser string
|
||||
ProcessUser string `json:"process_user"`
|
||||
// Verbosity is the OpenVPN verbosity level from 0 to 6.
|
||||
// It cannot be nil in the internal state.
|
||||
Verbosity *int
|
||||
Verbosity *int `json:"verbosity"`
|
||||
// Flags is a slice of additional flags to be passed
|
||||
// to the OpenVPN program.
|
||||
Flags []string
|
||||
Flags []string `json:"flags"`
|
||||
}
|
||||
|
||||
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{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, ", "))
|
||||
validVersions := []string{openvpn.Openvpn25, openvpn.Openvpn26}
|
||||
if err = validate.IsOneOf(o.Version, validVersions...); err != nil {
|
||||
return fmt.Errorf("%w: %w", ErrOpenVPNVersionIsNotValid, err)
|
||||
}
|
||||
|
||||
isCustom := vpnProvider == providers.Custom
|
||||
@@ -163,7 +162,7 @@ func validateOpenVPNConfigFilepath(isCustom bool,
|
||||
return fmt.Errorf("%w", ErrFilepathMissing)
|
||||
}
|
||||
|
||||
err = helpers.FileExists(confFile)
|
||||
err = validate.FileExists(confFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -244,92 +243,92 @@ func validateOpenVPNEncryptedKey(vpnProvider,
|
||||
func (o *OpenVPN) copy() (copied OpenVPN) {
|
||||
return OpenVPN{
|
||||
Version: o.Version,
|
||||
User: helpers.CopyPointer(o.User),
|
||||
Password: helpers.CopyPointer(o.Password),
|
||||
ConfFile: helpers.CopyPointer(o.ConfFile),
|
||||
Ciphers: helpers.CopySlice(o.Ciphers),
|
||||
Auth: helpers.CopyPointer(o.Auth),
|
||||
Cert: helpers.CopyPointer(o.Cert),
|
||||
Key: helpers.CopyPointer(o.Key),
|
||||
EncryptedKey: helpers.CopyPointer(o.EncryptedKey),
|
||||
KeyPassphrase: helpers.CopyPointer(o.KeyPassphrase),
|
||||
PIAEncPreset: helpers.CopyPointer(o.PIAEncPreset),
|
||||
MSSFix: helpers.CopyPointer(o.MSSFix),
|
||||
User: gosettings.CopyPointer(o.User),
|
||||
Password: gosettings.CopyPointer(o.Password),
|
||||
ConfFile: gosettings.CopyPointer(o.ConfFile),
|
||||
Ciphers: gosettings.CopySlice(o.Ciphers),
|
||||
Auth: gosettings.CopyPointer(o.Auth),
|
||||
Cert: gosettings.CopyPointer(o.Cert),
|
||||
Key: gosettings.CopyPointer(o.Key),
|
||||
EncryptedKey: gosettings.CopyPointer(o.EncryptedKey),
|
||||
KeyPassphrase: gosettings.CopyPointer(o.KeyPassphrase),
|
||||
PIAEncPreset: gosettings.CopyPointer(o.PIAEncPreset),
|
||||
MSSFix: gosettings.CopyPointer(o.MSSFix),
|
||||
Interface: o.Interface,
|
||||
ProcessUser: o.ProcessUser,
|
||||
Verbosity: helpers.CopyPointer(o.Verbosity),
|
||||
Flags: helpers.CopySlice(o.Flags),
|
||||
Verbosity: gosettings.CopyPointer(o.Verbosity),
|
||||
Flags: gosettings.CopySlice(o.Flags),
|
||||
}
|
||||
}
|
||||
|
||||
// mergeWith merges the other settings into any
|
||||
// unset field of the receiver settings object.
|
||||
func (o *OpenVPN) mergeWith(other OpenVPN) {
|
||||
o.Version = helpers.MergeWithString(o.Version, other.Version)
|
||||
o.User = helpers.MergeWithPointer(o.User, other.User)
|
||||
o.Password = helpers.MergeWithPointer(o.Password, other.Password)
|
||||
o.ConfFile = helpers.MergeWithPointer(o.ConfFile, other.ConfFile)
|
||||
o.Ciphers = helpers.MergeSlices(o.Ciphers, other.Ciphers)
|
||||
o.Auth = helpers.MergeWithPointer(o.Auth, other.Auth)
|
||||
o.Cert = helpers.MergeWithPointer(o.Cert, other.Cert)
|
||||
o.Key = helpers.MergeWithPointer(o.Key, other.Key)
|
||||
o.EncryptedKey = helpers.MergeWithPointer(o.EncryptedKey, other.EncryptedKey)
|
||||
o.KeyPassphrase = helpers.MergeWithPointer(o.KeyPassphrase, other.KeyPassphrase)
|
||||
o.PIAEncPreset = helpers.MergeWithPointer(o.PIAEncPreset, other.PIAEncPreset)
|
||||
o.MSSFix = helpers.MergeWithPointer(o.MSSFix, other.MSSFix)
|
||||
o.Interface = helpers.MergeWithString(o.Interface, other.Interface)
|
||||
o.ProcessUser = helpers.MergeWithString(o.ProcessUser, other.ProcessUser)
|
||||
o.Verbosity = helpers.MergeWithPointer(o.Verbosity, other.Verbosity)
|
||||
o.Flags = helpers.MergeSlices(o.Flags, other.Flags)
|
||||
o.Version = gosettings.MergeWithString(o.Version, other.Version)
|
||||
o.User = gosettings.MergeWithPointer(o.User, other.User)
|
||||
o.Password = gosettings.MergeWithPointer(o.Password, other.Password)
|
||||
o.ConfFile = gosettings.MergeWithPointer(o.ConfFile, other.ConfFile)
|
||||
o.Ciphers = gosettings.MergeWithSlice(o.Ciphers, other.Ciphers)
|
||||
o.Auth = gosettings.MergeWithPointer(o.Auth, other.Auth)
|
||||
o.Cert = gosettings.MergeWithPointer(o.Cert, other.Cert)
|
||||
o.Key = gosettings.MergeWithPointer(o.Key, other.Key)
|
||||
o.EncryptedKey = gosettings.MergeWithPointer(o.EncryptedKey, other.EncryptedKey)
|
||||
o.KeyPassphrase = gosettings.MergeWithPointer(o.KeyPassphrase, other.KeyPassphrase)
|
||||
o.PIAEncPreset = gosettings.MergeWithPointer(o.PIAEncPreset, other.PIAEncPreset)
|
||||
o.MSSFix = gosettings.MergeWithPointer(o.MSSFix, other.MSSFix)
|
||||
o.Interface = gosettings.MergeWithString(o.Interface, other.Interface)
|
||||
o.ProcessUser = gosettings.MergeWithString(o.ProcessUser, other.ProcessUser)
|
||||
o.Verbosity = gosettings.MergeWithPointer(o.Verbosity, other.Verbosity)
|
||||
o.Flags = gosettings.MergeWithSlice(o.Flags, other.Flags)
|
||||
}
|
||||
|
||||
// overrideWith overrides fields of the receiver
|
||||
// settings object with any field set in the other
|
||||
// settings.
|
||||
func (o *OpenVPN) overrideWith(other OpenVPN) {
|
||||
o.Version = helpers.OverrideWithString(o.Version, other.Version)
|
||||
o.User = helpers.OverrideWithPointer(o.User, other.User)
|
||||
o.Password = helpers.OverrideWithPointer(o.Password, other.Password)
|
||||
o.ConfFile = helpers.OverrideWithPointer(o.ConfFile, other.ConfFile)
|
||||
o.Ciphers = helpers.OverrideWithSlice(o.Ciphers, other.Ciphers)
|
||||
o.Auth = helpers.OverrideWithPointer(o.Auth, other.Auth)
|
||||
o.Cert = helpers.OverrideWithPointer(o.Cert, other.Cert)
|
||||
o.Key = helpers.OverrideWithPointer(o.Key, other.Key)
|
||||
o.EncryptedKey = helpers.OverrideWithPointer(o.EncryptedKey, other.EncryptedKey)
|
||||
o.KeyPassphrase = helpers.OverrideWithPointer(o.KeyPassphrase, other.KeyPassphrase)
|
||||
o.PIAEncPreset = helpers.OverrideWithPointer(o.PIAEncPreset, other.PIAEncPreset)
|
||||
o.MSSFix = helpers.OverrideWithPointer(o.MSSFix, other.MSSFix)
|
||||
o.Interface = helpers.OverrideWithString(o.Interface, other.Interface)
|
||||
o.ProcessUser = helpers.OverrideWithString(o.ProcessUser, other.ProcessUser)
|
||||
o.Verbosity = helpers.OverrideWithPointer(o.Verbosity, other.Verbosity)
|
||||
o.Flags = helpers.OverrideWithSlice(o.Flags, other.Flags)
|
||||
o.Version = gosettings.OverrideWithString(o.Version, other.Version)
|
||||
o.User = gosettings.OverrideWithPointer(o.User, other.User)
|
||||
o.Password = gosettings.OverrideWithPointer(o.Password, other.Password)
|
||||
o.ConfFile = gosettings.OverrideWithPointer(o.ConfFile, other.ConfFile)
|
||||
o.Ciphers = gosettings.OverrideWithSlice(o.Ciphers, other.Ciphers)
|
||||
o.Auth = gosettings.OverrideWithPointer(o.Auth, other.Auth)
|
||||
o.Cert = gosettings.OverrideWithPointer(o.Cert, other.Cert)
|
||||
o.Key = gosettings.OverrideWithPointer(o.Key, other.Key)
|
||||
o.EncryptedKey = gosettings.OverrideWithPointer(o.EncryptedKey, other.EncryptedKey)
|
||||
o.KeyPassphrase = gosettings.OverrideWithPointer(o.KeyPassphrase, other.KeyPassphrase)
|
||||
o.PIAEncPreset = gosettings.OverrideWithPointer(o.PIAEncPreset, other.PIAEncPreset)
|
||||
o.MSSFix = gosettings.OverrideWithPointer(o.MSSFix, other.MSSFix)
|
||||
o.Interface = gosettings.OverrideWithString(o.Interface, other.Interface)
|
||||
o.ProcessUser = gosettings.OverrideWithString(o.ProcessUser, other.ProcessUser)
|
||||
o.Verbosity = gosettings.OverrideWithPointer(o.Verbosity, other.Verbosity)
|
||||
o.Flags = gosettings.OverrideWithSlice(o.Flags, other.Flags)
|
||||
}
|
||||
|
||||
func (o *OpenVPN) setDefaults(vpnProvider string) {
|
||||
o.Version = helpers.DefaultString(o.Version, openvpn.Openvpn25)
|
||||
o.User = helpers.DefaultPointer(o.User, "")
|
||||
o.Version = gosettings.DefaultString(o.Version, openvpn.Openvpn25)
|
||||
o.User = gosettings.DefaultPointer(o.User, "")
|
||||
if vpnProvider == providers.Mullvad {
|
||||
o.Password = helpers.DefaultPointer(o.Password, "m")
|
||||
o.Password = gosettings.DefaultPointer(o.Password, "m")
|
||||
} else {
|
||||
o.Password = helpers.DefaultPointer(o.Password, "")
|
||||
o.Password = gosettings.DefaultPointer(o.Password, "")
|
||||
}
|
||||
|
||||
o.ConfFile = helpers.DefaultPointer(o.ConfFile, "")
|
||||
o.Auth = helpers.DefaultPointer(o.Auth, "")
|
||||
o.Cert = helpers.DefaultPointer(o.Cert, "")
|
||||
o.Key = helpers.DefaultPointer(o.Key, "")
|
||||
o.EncryptedKey = helpers.DefaultPointer(o.EncryptedKey, "")
|
||||
o.KeyPassphrase = helpers.DefaultPointer(o.KeyPassphrase, "")
|
||||
o.ConfFile = gosettings.DefaultPointer(o.ConfFile, "")
|
||||
o.Auth = gosettings.DefaultPointer(o.Auth, "")
|
||||
o.Cert = gosettings.DefaultPointer(o.Cert, "")
|
||||
o.Key = gosettings.DefaultPointer(o.Key, "")
|
||||
o.EncryptedKey = gosettings.DefaultPointer(o.EncryptedKey, "")
|
||||
o.KeyPassphrase = gosettings.DefaultPointer(o.KeyPassphrase, "")
|
||||
|
||||
var defaultEncPreset string
|
||||
if vpnProvider == providers.PrivateInternetAccess {
|
||||
defaultEncPreset = presets.Strong
|
||||
}
|
||||
o.PIAEncPreset = helpers.DefaultPointer(o.PIAEncPreset, defaultEncPreset)
|
||||
o.MSSFix = helpers.DefaultPointer(o.MSSFix, 0)
|
||||
o.Interface = helpers.DefaultString(o.Interface, "tun0")
|
||||
o.ProcessUser = helpers.DefaultString(o.ProcessUser, "root")
|
||||
o.Verbosity = helpers.DefaultPointer(o.Verbosity, 1)
|
||||
o.PIAEncPreset = gosettings.DefaultPointer(o.PIAEncPreset, defaultEncPreset)
|
||||
o.MSSFix = gosettings.DefaultPointer(o.MSSFix, 0)
|
||||
o.Interface = gosettings.DefaultString(o.Interface, "tun0")
|
||||
o.ProcessUser = gosettings.DefaultString(o.ProcessUser, "root")
|
||||
o.Verbosity = gosettings.DefaultPointer(o.Verbosity, 1)
|
||||
}
|
||||
|
||||
func (o OpenVPN) String() string {
|
||||
@@ -339,8 +338,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", gosettings.ObfuscateKey(*o.User))
|
||||
node.Appendf("Password: %s", gosettings.ObfuscateKey(*o.Password))
|
||||
|
||||
if *o.ConfFile != "" {
|
||||
node.Appendf("Custom configuration file: %s", *o.ConfFile)
|
||||
@@ -355,16 +354,16 @@ func (o OpenVPN) toLinesNode() (node *gotree.Node) {
|
||||
}
|
||||
|
||||
if *o.Cert != "" {
|
||||
node.Appendf("Client crt: %s", helpers.ObfuscateData(*o.Cert))
|
||||
node.Appendf("Client crt: %s", gosettings.ObfuscateKey(*o.Cert))
|
||||
}
|
||||
|
||||
if *o.Key != "" {
|
||||
node.Appendf("Client key: %s", helpers.ObfuscateData(*o.Key))
|
||||
node.Appendf("Client key: %s", gosettings.ObfuscateKey(*o.Key))
|
||||
}
|
||||
|
||||
if *o.EncryptedKey != "" {
|
||||
node.Appendf("Encrypted key: %s (key passhrapse %s)",
|
||||
helpers.ObfuscateData(*o.EncryptedKey), helpers.ObfuscatePassword(*o.KeyPassphrase))
|
||||
gosettings.ObfuscateKey(*o.EncryptedKey), gosettings.ObfuscateKey(*o.KeyPassphrase))
|
||||
}
|
||||
|
||||
if *o.PIAEncPreset != "" {
|
||||
|
||||
@@ -6,6 +6,8 @@ import (
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||
"github.com/qdm12/gluetun/internal/provider/privateinternetaccess/presets"
|
||||
"github.com/qdm12/gosettings"
|
||||
"github.com/qdm12/gosettings/validate"
|
||||
"github.com/qdm12/gotree"
|
||||
)
|
||||
|
||||
@@ -14,25 +16,25 @@ type OpenVPNSelection struct {
|
||||
// It can be set to an empty string to indicate to
|
||||
// NOT use a custom configuration file.
|
||||
// It cannot be nil in the internal state.
|
||||
ConfFile *string
|
||||
ConfFile *string `json:"config_file_path"`
|
||||
// TCP is true if the OpenVPN protocol is TCP,
|
||||
// and false for UDP.
|
||||
// It cannot be nil in the internal state.
|
||||
TCP *bool
|
||||
TCP *bool `json:"tcp"`
|
||||
// CustomPort is the OpenVPN server endpoint port.
|
||||
// It can be set to 0 to indicate no custom port should
|
||||
// be used. It cannot be nil in the internal state.
|
||||
CustomPort *uint16 // HideMyAss, Mullvad, PIA, ProtonVPN, WeVPN, Windscribe
|
||||
CustomPort *uint16 `json:"custom_port"`
|
||||
// PIAEncPreset is the encryption preset for
|
||||
// Private Internet Access. It can be set to an
|
||||
// empty string for other providers.
|
||||
PIAEncPreset *string
|
||||
PIAEncPreset *string `json:"pia_encryption_preset"`
|
||||
}
|
||||
|
||||
func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
|
||||
// Validate ConfFile
|
||||
if confFile := *o.ConfFile; confFile != "" {
|
||||
err := helpers.FileExists(confFile)
|
||||
err := validate.FileExists(confFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("configuration file: %w", err)
|
||||
}
|
||||
@@ -54,7 +56,7 @@ func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
|
||||
if *o.CustomPort != 0 {
|
||||
switch vpnProvider {
|
||||
// no restriction on port
|
||||
case providers.Cyberghost, providers.HideMyAss,
|
||||
case providers.Custom, providers.Cyberghost, providers.HideMyAss,
|
||||
providers.Privatevpn, providers.Torguard:
|
||||
// no custom port allowed
|
||||
case providers.Expressvpn, providers.Fastestvpn,
|
||||
@@ -97,16 +99,18 @@ func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
|
||||
case providers.Windscribe:
|
||||
allowedTCP = []uint16{21, 22, 80, 123, 143, 443, 587, 1194, 3306, 8080, 54783}
|
||||
allowedUDP = []uint16{53, 80, 123, 443, 1194, 54783}
|
||||
default:
|
||||
panic(fmt.Sprintf("VPN provider %s has no registered allowed ports", vpnProvider))
|
||||
}
|
||||
|
||||
if *o.TCP && !helpers.Uint16IsOneOf(*o.CustomPort, allowedTCP) {
|
||||
return fmt.Errorf("%w: %d for VPN service provider %s; %s",
|
||||
ErrOpenVPNCustomPortNotAllowed, o.CustomPort, vpnProvider,
|
||||
helpers.PortChoicesOrString(allowedTCP))
|
||||
} else if !*o.TCP && !helpers.Uint16IsOneOf(*o.CustomPort, allowedUDP) {
|
||||
return fmt.Errorf("%w: %d for VPN service provider %s; %s",
|
||||
ErrOpenVPNCustomPortNotAllowed, o.CustomPort, vpnProvider,
|
||||
helpers.PortChoicesOrString(allowedUDP))
|
||||
allowedPorts := allowedUDP
|
||||
if *o.TCP {
|
||||
allowedPorts = allowedTCP
|
||||
}
|
||||
err = validate.IsOneOf(*o.CustomPort, allowedPorts...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: for VPN service provider %s: %w",
|
||||
ErrOpenVPNCustomPortNotAllowed, vpnProvider, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -118,10 +122,8 @@ func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
|
||||
presets.Normal,
|
||||
presets.Strong,
|
||||
}
|
||||
if !helpers.IsOneOf(*o.PIAEncPreset, validEncryptionPresets...) {
|
||||
return fmt.Errorf("%w: %s; valid presets are %s",
|
||||
ErrOpenVPNEncryptionPresetNotValid, *o.PIAEncPreset,
|
||||
helpers.ChoicesOrString(validEncryptionPresets))
|
||||
if err = validate.IsOneOf(*o.PIAEncPreset, validEncryptionPresets...); err != nil {
|
||||
return fmt.Errorf("%w: %w", ErrOpenVPNEncryptionPresetNotValid, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,37 +132,37 @@ func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
|
||||
|
||||
func (o *OpenVPNSelection) copy() (copied OpenVPNSelection) {
|
||||
return OpenVPNSelection{
|
||||
ConfFile: helpers.CopyPointer(o.ConfFile),
|
||||
TCP: helpers.CopyPointer(o.TCP),
|
||||
CustomPort: helpers.CopyPointer(o.CustomPort),
|
||||
PIAEncPreset: helpers.CopyPointer(o.PIAEncPreset),
|
||||
ConfFile: gosettings.CopyPointer(o.ConfFile),
|
||||
TCP: gosettings.CopyPointer(o.TCP),
|
||||
CustomPort: gosettings.CopyPointer(o.CustomPort),
|
||||
PIAEncPreset: gosettings.CopyPointer(o.PIAEncPreset),
|
||||
}
|
||||
}
|
||||
|
||||
func (o *OpenVPNSelection) mergeWith(other OpenVPNSelection) {
|
||||
o.ConfFile = helpers.MergeWithPointer(o.ConfFile, other.ConfFile)
|
||||
o.TCP = helpers.MergeWithPointer(o.TCP, other.TCP)
|
||||
o.CustomPort = helpers.MergeWithPointer(o.CustomPort, other.CustomPort)
|
||||
o.PIAEncPreset = helpers.MergeWithPointer(o.PIAEncPreset, other.PIAEncPreset)
|
||||
o.ConfFile = gosettings.MergeWithPointer(o.ConfFile, other.ConfFile)
|
||||
o.TCP = gosettings.MergeWithPointer(o.TCP, other.TCP)
|
||||
o.CustomPort = gosettings.MergeWithPointer(o.CustomPort, other.CustomPort)
|
||||
o.PIAEncPreset = gosettings.MergeWithPointer(o.PIAEncPreset, other.PIAEncPreset)
|
||||
}
|
||||
|
||||
func (o *OpenVPNSelection) overrideWith(other OpenVPNSelection) {
|
||||
o.ConfFile = helpers.OverrideWithPointer(o.ConfFile, other.ConfFile)
|
||||
o.TCP = helpers.OverrideWithPointer(o.TCP, other.TCP)
|
||||
o.CustomPort = helpers.OverrideWithPointer(o.CustomPort, other.CustomPort)
|
||||
o.PIAEncPreset = helpers.OverrideWithPointer(o.PIAEncPreset, other.PIAEncPreset)
|
||||
o.ConfFile = gosettings.OverrideWithPointer(o.ConfFile, other.ConfFile)
|
||||
o.TCP = gosettings.OverrideWithPointer(o.TCP, other.TCP)
|
||||
o.CustomPort = gosettings.OverrideWithPointer(o.CustomPort, other.CustomPort)
|
||||
o.PIAEncPreset = gosettings.OverrideWithPointer(o.PIAEncPreset, other.PIAEncPreset)
|
||||
}
|
||||
|
||||
func (o *OpenVPNSelection) setDefaults(vpnProvider string) {
|
||||
o.ConfFile = helpers.DefaultPointer(o.ConfFile, "")
|
||||
o.TCP = helpers.DefaultPointer(o.TCP, false)
|
||||
o.CustomPort = helpers.DefaultPointer(o.CustomPort, 0)
|
||||
o.ConfFile = gosettings.DefaultPointer(o.ConfFile, "")
|
||||
o.TCP = gosettings.DefaultPointer(o.TCP, false)
|
||||
o.CustomPort = gosettings.DefaultPointer(o.CustomPort, 0)
|
||||
|
||||
var defaultEncPreset string
|
||||
if vpnProvider == providers.PrivateInternetAccess {
|
||||
defaultEncPreset = presets.Strong
|
||||
}
|
||||
o.PIAEncPreset = helpers.DefaultPointer(o.PIAEncPreset, defaultEncPreset)
|
||||
o.PIAEncPreset = gosettings.DefaultPointer(o.PIAEncPreset, defaultEncPreset)
|
||||
}
|
||||
|
||||
func (o OpenVPNSelection) String() string {
|
||||
|
||||
@@ -3,10 +3,10 @@ package settings
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||
"github.com/qdm12/gosettings"
|
||||
"github.com/qdm12/gosettings/validate"
|
||||
"github.com/qdm12/gotree"
|
||||
)
|
||||
|
||||
@@ -14,24 +14,38 @@ import (
|
||||
type PortForwarding struct {
|
||||
// Enabled is true if port forwarding should be activated.
|
||||
// It cannot be nil for the internal state.
|
||||
Enabled *bool
|
||||
Enabled *bool `json:"enabled"`
|
||||
// Provider is set to specify which custom port forwarding code
|
||||
// should be used. This is especially necessary for the custom
|
||||
// provider using Wireguard for a provider where Wireguard is not
|
||||
// natively supported but custom port forwading code is available.
|
||||
// It defaults to the empty string, meaning the current provider
|
||||
// should be the one used for port forwarding.
|
||||
// It cannot be nil for the internal state.
|
||||
Provider *string `json:"provider"`
|
||||
// Filepath is the port forwarding status file path
|
||||
// to use. It can be the empty string to indicate not
|
||||
// to write to a file. It cannot be nil for the
|
||||
// internal state
|
||||
Filepath *string
|
||||
Filepath *string `json:"status_file_path"`
|
||||
}
|
||||
|
||||
func (p PortForwarding) validate(vpnProvider string) (err error) {
|
||||
func (p PortForwarding) Validate(vpnProvider string) (err error) {
|
||||
if !*p.Enabled {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate Enabled
|
||||
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, ", "))
|
||||
// Validate current provider or custom provider specified
|
||||
providerSelected := vpnProvider
|
||||
if *p.Provider != "" {
|
||||
providerSelected = *p.Provider
|
||||
}
|
||||
validProviders := []string{
|
||||
providers.PrivateInternetAccess,
|
||||
providers.Protonvpn,
|
||||
}
|
||||
if err = validate.IsOneOf(providerSelected, validProviders...); err != nil {
|
||||
return fmt.Errorf("%w: %w", ErrPortForwardingEnabled, err)
|
||||
}
|
||||
|
||||
// Validate Filepath
|
||||
@@ -45,26 +59,30 @@ func (p PortForwarding) validate(vpnProvider string) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *PortForwarding) copy() (copied PortForwarding) {
|
||||
func (p *PortForwarding) Copy() (copied PortForwarding) {
|
||||
return PortForwarding{
|
||||
Enabled: helpers.CopyPointer(p.Enabled),
|
||||
Filepath: helpers.CopyPointer(p.Filepath),
|
||||
Enabled: gosettings.CopyPointer(p.Enabled),
|
||||
Provider: gosettings.CopyPointer(p.Provider),
|
||||
Filepath: gosettings.CopyPointer(p.Filepath),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PortForwarding) mergeWith(other PortForwarding) {
|
||||
p.Enabled = helpers.MergeWithPointer(p.Enabled, other.Enabled)
|
||||
p.Filepath = helpers.MergeWithPointer(p.Filepath, other.Filepath)
|
||||
p.Enabled = gosettings.MergeWithPointer(p.Enabled, other.Enabled)
|
||||
p.Provider = gosettings.MergeWithPointer(p.Provider, other.Provider)
|
||||
p.Filepath = gosettings.MergeWithPointer(p.Filepath, other.Filepath)
|
||||
}
|
||||
|
||||
func (p *PortForwarding) overrideWith(other PortForwarding) {
|
||||
p.Enabled = helpers.OverrideWithPointer(p.Enabled, other.Enabled)
|
||||
p.Filepath = helpers.OverrideWithPointer(p.Filepath, other.Filepath)
|
||||
func (p *PortForwarding) OverrideWith(other PortForwarding) {
|
||||
p.Enabled = gosettings.OverrideWithPointer(p.Enabled, other.Enabled)
|
||||
p.Provider = gosettings.OverrideWithPointer(p.Provider, other.Provider)
|
||||
p.Filepath = gosettings.OverrideWithPointer(p.Filepath, other.Filepath)
|
||||
}
|
||||
|
||||
func (p *PortForwarding) setDefaults() {
|
||||
p.Enabled = helpers.DefaultPointer(p.Enabled, false)
|
||||
p.Filepath = helpers.DefaultPointer(p.Filepath, "/tmp/gluetun/forwarded_port")
|
||||
p.Enabled = gosettings.DefaultPointer(p.Enabled, false)
|
||||
p.Provider = gosettings.DefaultPointer(p.Provider, "")
|
||||
p.Filepath = gosettings.DefaultPointer(p.Filepath, "/tmp/gluetun/forwarded_port")
|
||||
}
|
||||
|
||||
func (p PortForwarding) String() string {
|
||||
@@ -77,7 +95,11 @@ func (p PortForwarding) toLinesNode() (node *gotree.Node) {
|
||||
}
|
||||
|
||||
node = gotree.New("Automatic port forwarding settings:")
|
||||
node.Appendf("Enabled: yes")
|
||||
if *p.Provider == "" {
|
||||
node.Appendf("Use port forwarding code for current provider")
|
||||
} else {
|
||||
node.Appendf("Use code for provider: %s", *p.Provider)
|
||||
}
|
||||
|
||||
filepath := *p.Filepath
|
||||
if filepath == "" {
|
||||
|
||||
@@ -3,9 +3,10 @@ package settings
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||
"github.com/qdm12/gluetun/internal/constants/vpn"
|
||||
"github.com/qdm12/gosettings"
|
||||
"github.com/qdm12/gosettings/validate"
|
||||
"github.com/qdm12/gotree"
|
||||
)
|
||||
|
||||
@@ -13,12 +14,12 @@ import (
|
||||
type Provider struct {
|
||||
// Name is the VPN service provider name.
|
||||
// It cannot be nil in the internal state.
|
||||
Name *string
|
||||
Name *string `json:"name"`
|
||||
// ServerSelection is the settings to
|
||||
// select the VPN server.
|
||||
ServerSelection ServerSelection
|
||||
ServerSelection ServerSelection `json:"server_selection"`
|
||||
// PortForwarding is the settings about port forwarding.
|
||||
PortForwarding PortForwarding
|
||||
PortForwarding PortForwarding `json:"port_forwarding"`
|
||||
}
|
||||
|
||||
// TODO v4 remove pointer for receiver (because of Surfshark).
|
||||
@@ -34,13 +35,13 @@ func (p *Provider) validate(vpnType string, storage Storage) (err error) {
|
||||
providers.Custom,
|
||||
providers.Ivpn,
|
||||
providers.Mullvad,
|
||||
providers.Nordvpn,
|
||||
providers.Surfshark,
|
||||
providers.Windscribe,
|
||||
}
|
||||
}
|
||||
if !helpers.IsOneOf(*p.Name, validNames...) {
|
||||
return fmt.Errorf("%w for Wireguard: %q can only be one of %s",
|
||||
ErrVPNProviderNameNotValid, *p.Name, helpers.ChoicesOrString(validNames))
|
||||
if err = validate.IsOneOf(*p.Name, validNames...); err != nil {
|
||||
return fmt.Errorf("%w for Wireguard: %w", ErrVPNProviderNameNotValid, err)
|
||||
}
|
||||
|
||||
err = p.ServerSelection.validate(*p.Name, storage)
|
||||
@@ -48,7 +49,7 @@ func (p *Provider) validate(vpnType string, storage Storage) (err error) {
|
||||
return fmt.Errorf("server selection: %w", err)
|
||||
}
|
||||
|
||||
err = p.PortForwarding.validate(*p.Name)
|
||||
err = p.PortForwarding.Validate(*p.Name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("port forwarding: %w", err)
|
||||
}
|
||||
@@ -58,26 +59,26 @@ func (p *Provider) validate(vpnType string, storage Storage) (err error) {
|
||||
|
||||
func (p *Provider) copy() (copied Provider) {
|
||||
return Provider{
|
||||
Name: helpers.CopyPointer(p.Name),
|
||||
Name: gosettings.CopyPointer(p.Name),
|
||||
ServerSelection: p.ServerSelection.copy(),
|
||||
PortForwarding: p.PortForwarding.copy(),
|
||||
PortForwarding: p.PortForwarding.Copy(),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Provider) mergeWith(other Provider) {
|
||||
p.Name = helpers.MergeWithPointer(p.Name, other.Name)
|
||||
p.Name = gosettings.MergeWithPointer(p.Name, other.Name)
|
||||
p.ServerSelection.mergeWith(other.ServerSelection)
|
||||
p.PortForwarding.mergeWith(other.PortForwarding)
|
||||
}
|
||||
|
||||
func (p *Provider) overrideWith(other Provider) {
|
||||
p.Name = helpers.OverrideWithPointer(p.Name, other.Name)
|
||||
p.Name = gosettings.OverrideWithPointer(p.Name, other.Name)
|
||||
p.ServerSelection.overrideWith(other.ServerSelection)
|
||||
p.PortForwarding.overrideWith(other.PortForwarding)
|
||||
p.PortForwarding.OverrideWith(other.PortForwarding)
|
||||
}
|
||||
|
||||
func (p *Provider) setDefaults() {
|
||||
p.Name = helpers.DefaultPointer(p.Name, providers.PrivateInternetAccess)
|
||||
p.Name = gosettings.DefaultPointer(p.Name, providers.PrivateInternetAccess)
|
||||
p.ServerSelection.setDefaults(*p.Name)
|
||||
p.PortForwarding.setDefaults()
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||
"github.com/qdm12/gosettings"
|
||||
"github.com/qdm12/gotree"
|
||||
)
|
||||
|
||||
@@ -23,6 +23,20 @@ type PublicIP struct {
|
||||
IPFilepath *string
|
||||
}
|
||||
|
||||
// UpdateWith deep copies the receiving settings, overrides the copy with
|
||||
// fields set in the partialUpdate argument, validates the new settings
|
||||
// and returns them if they are valid, or returns an error otherwise.
|
||||
// In all cases, the receiving settings are unmodified.
|
||||
func (p PublicIP) UpdateWith(partialUpdate PublicIP) (updatedSettings PublicIP, err error) {
|
||||
updatedSettings = p.copy()
|
||||
updatedSettings.overrideWith(partialUpdate)
|
||||
err = updatedSettings.validate()
|
||||
if err != nil {
|
||||
return updatedSettings, fmt.Errorf("validating updated settings: %w", err)
|
||||
}
|
||||
return updatedSettings, nil
|
||||
}
|
||||
|
||||
func (p PublicIP) validate() (err error) {
|
||||
const minPeriod = 5 * time.Second
|
||||
if *p.Period < minPeriod {
|
||||
@@ -42,25 +56,25 @@ func (p PublicIP) validate() (err error) {
|
||||
|
||||
func (p *PublicIP) copy() (copied PublicIP) {
|
||||
return PublicIP{
|
||||
Period: helpers.CopyPointer(p.Period),
|
||||
IPFilepath: helpers.CopyPointer(p.IPFilepath),
|
||||
Period: gosettings.CopyPointer(p.Period),
|
||||
IPFilepath: gosettings.CopyPointer(p.IPFilepath),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PublicIP) mergeWith(other PublicIP) {
|
||||
p.Period = helpers.MergeWithPointer(p.Period, other.Period)
|
||||
p.IPFilepath = helpers.MergeWithPointer(p.IPFilepath, other.IPFilepath)
|
||||
p.Period = gosettings.MergeWithPointer(p.Period, other.Period)
|
||||
p.IPFilepath = gosettings.MergeWithPointer(p.IPFilepath, other.IPFilepath)
|
||||
}
|
||||
|
||||
func (p *PublicIP) overrideWith(other PublicIP) {
|
||||
p.Period = helpers.OverrideWithPointer(p.Period, other.Period)
|
||||
p.IPFilepath = helpers.OverrideWithPointer(p.IPFilepath, other.IPFilepath)
|
||||
p.Period = gosettings.OverrideWithPointer(p.Period, other.Period)
|
||||
p.IPFilepath = gosettings.OverrideWithPointer(p.IPFilepath, other.IPFilepath)
|
||||
}
|
||||
|
||||
func (p *PublicIP) setDefaults() {
|
||||
const defaultPeriod = 12 * time.Hour
|
||||
p.Period = helpers.DefaultPointer(p.Period, defaultPeriod)
|
||||
p.IPFilepath = helpers.DefaultPointer(p.IPFilepath, "/tmp/gluetun/ip")
|
||||
p.Period = gosettings.DefaultPointer(p.Period, defaultPeriod)
|
||||
p.IPFilepath = gosettings.DefaultPointer(p.IPFilepath, "/tmp/gluetun/ip")
|
||||
}
|
||||
|
||||
func (p PublicIP) String() string {
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||
"github.com/qdm12/gosettings"
|
||||
"github.com/qdm12/gotree"
|
||||
)
|
||||
|
||||
@@ -43,29 +43,29 @@ func (c ControlServer) validate() (err error) {
|
||||
|
||||
func (c *ControlServer) copy() (copied ControlServer) {
|
||||
return ControlServer{
|
||||
Address: helpers.CopyPointer(c.Address),
|
||||
Log: helpers.CopyPointer(c.Log),
|
||||
Address: gosettings.CopyPointer(c.Address),
|
||||
Log: gosettings.CopyPointer(c.Log),
|
||||
}
|
||||
}
|
||||
|
||||
// mergeWith merges the other settings into any
|
||||
// unset field of the receiver settings object.
|
||||
func (c *ControlServer) mergeWith(other ControlServer) {
|
||||
c.Address = helpers.MergeWithPointer(c.Address, other.Address)
|
||||
c.Log = helpers.MergeWithPointer(c.Log, other.Log)
|
||||
c.Address = gosettings.MergeWithPointer(c.Address, other.Address)
|
||||
c.Log = gosettings.MergeWithPointer(c.Log, other.Log)
|
||||
}
|
||||
|
||||
// overrideWith overrides fields of the receiver
|
||||
// settings object with any field set in the other
|
||||
// settings.
|
||||
func (c *ControlServer) overrideWith(other ControlServer) {
|
||||
c.Address = helpers.OverrideWithPointer(c.Address, other.Address)
|
||||
c.Log = helpers.OverrideWithPointer(c.Log, other.Log)
|
||||
c.Address = gosettings.OverrideWithPointer(c.Address, other.Address)
|
||||
c.Log = gosettings.OverrideWithPointer(c.Log, other.Log)
|
||||
}
|
||||
|
||||
func (c *ControlServer) setDefaults() {
|
||||
c.Address = helpers.DefaultPointer(c.Address, ":8000")
|
||||
c.Log = helpers.DefaultPointer(c.Log, true)
|
||||
c.Address = gosettings.DefaultPointer(c.Address, ":8000")
|
||||
c.Log = gosettings.DefaultPointer(c.Log, true)
|
||||
}
|
||||
|
||||
func (c ControlServer) String() string {
|
||||
@@ -75,6 +75,6 @@ func (c ControlServer) String() string {
|
||||
func (c ControlServer) toLinesNode() (node *gotree.Node) {
|
||||
node = gotree.New("Control server settings:")
|
||||
node.Appendf("Listening address: %s", *c.Address)
|
||||
node.Appendf("Logging: %s", helpers.BoolPtrToYesNo(c.Log))
|
||||
node.Appendf("Logging: %s", gosettings.BoolToYesNo(c.Log))
|
||||
return node
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ import (
|
||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||
"github.com/qdm12/gluetun/internal/constants/vpn"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/qdm12/gosettings"
|
||||
"github.com/qdm12/gosettings/validate"
|
||||
"github.com/qdm12/gotree"
|
||||
)
|
||||
|
||||
@@ -18,50 +20,50 @@ type ServerSelection struct { //nolint:maligned
|
||||
// VPN is the VPN type which can be 'openvpn'
|
||||
// or 'wireguard'. It cannot be the empty string
|
||||
// in the internal state.
|
||||
VPN string
|
||||
VPN string `json:"vpn"`
|
||||
// TargetIP is the server endpoint IP address to use.
|
||||
// It will override any IP address from the picked
|
||||
// built-in server. It cannot be the empty value in the internal
|
||||
// state, and can be set to the unspecified address to indicate
|
||||
// there is not target IP address to use.
|
||||
TargetIP netip.Addr
|
||||
TargetIP netip.Addr `json:"target_ip"`
|
||||
// Counties is the list of countries to filter VPN servers with.
|
||||
Countries []string
|
||||
Countries []string `json:"countries"`
|
||||
// Regions is the list of regions to filter VPN servers with.
|
||||
Regions []string
|
||||
Regions []string `json:"regions"`
|
||||
// Cities is the list of cities to filter VPN servers with.
|
||||
Cities []string
|
||||
Cities []string `json:"cities"`
|
||||
// ISPs is the list of ISP names to filter VPN servers with.
|
||||
ISPs []string
|
||||
ISPs []string `json:"isps"`
|
||||
// Names is the list of server names to filter VPN servers with.
|
||||
Names []string
|
||||
Names []string `json:"names"`
|
||||
// Numbers is the list of server numbers to filter VPN servers with.
|
||||
Numbers []uint16
|
||||
Numbers []uint16 `json:"numbers"`
|
||||
// Hostnames is the list of hostnames to filter VPN servers with.
|
||||
Hostnames []string
|
||||
Hostnames []string `json:"hostnames"`
|
||||
// OwnedOnly is true if VPN provider servers that are not owned
|
||||
// should be filtered. This is used with Mullvad.
|
||||
OwnedOnly *bool
|
||||
OwnedOnly *bool `json:"owned_only"`
|
||||
// FreeOnly is true if VPN servers that are not free should
|
||||
// be filtered. This is used with ProtonVPN and VPN Unlimited.
|
||||
FreeOnly *bool
|
||||
FreeOnly *bool `json:"free_only"`
|
||||
// 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
|
||||
PremiumOnly *bool `json:"premium_only"`
|
||||
// StreamOnly is true if VPN servers not for streaming should
|
||||
// be filtered. This is used with VPNUnlimited.
|
||||
StreamOnly *bool
|
||||
StreamOnly *bool `json:"stream_only"`
|
||||
// MultiHopOnly is true if VPN servers that are not multihop
|
||||
// should be filtered. This is used with Surfshark.
|
||||
MultiHopOnly *bool
|
||||
MultiHopOnly *bool `json:"multi_hop_only"`
|
||||
|
||||
// OpenVPN contains settings to select OpenVPN servers
|
||||
// and the final connection.
|
||||
OpenVPN OpenVPNSelection
|
||||
OpenVPN OpenVPNSelection `json:"openvpn"`
|
||||
// Wireguard contains settings to select Wireguard servers
|
||||
// and the final connection.
|
||||
Wireguard WireguardSelection
|
||||
Wireguard WireguardSelection `json:"wireguard"`
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -86,12 +88,17 @@ func (ss *ServerSelection) validate(vpnServiceProvider string,
|
||||
return err // already wrapped error
|
||||
}
|
||||
|
||||
// Retro-compatibility
|
||||
switch vpnServiceProvider {
|
||||
case providers.Nordvpn:
|
||||
*ss = nordvpnRetroRegion(*ss, filterChoices.Regions, filterChoices.Countries)
|
||||
case providers.Surfshark:
|
||||
*ss = surfsharkRetroRegion(*ss)
|
||||
}
|
||||
|
||||
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
|
||||
return fmt.Errorf("for VPN service provider %s: %w", vpnServiceProvider, err)
|
||||
}
|
||||
|
||||
if *ss.OwnedOnly &&
|
||||
@@ -160,10 +167,10 @@ func getLocationFilterChoices(vpnServiceProvider string,
|
||||
// // 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)
|
||||
err := validate.AreAllOneOfCaseInsensitive(ss.Regions, filterChoices.Regions)
|
||||
if err != nil {
|
||||
return models.FilterChoices{}, fmt.Errorf("%w: %w", ErrRegionNotValid, err)
|
||||
}
|
||||
*ss = surfsharkRetroRegion(*ss)
|
||||
}
|
||||
|
||||
return filterChoices, nil
|
||||
@@ -172,28 +179,34 @@ func getLocationFilterChoices(vpnServiceProvider string,
|
||||
// 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, filterChoices models.FilterChoices) (err error) {
|
||||
if err := helpers.AreAllOneOf(settings.Countries, filterChoices.Countries); err != nil {
|
||||
return fmt.Errorf("%w: %s", ErrCountryNotValid, err)
|
||||
err = validate.AreAllOneOfCaseInsensitive(settings.Countries, filterChoices.Countries)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %w", ErrCountryNotValid, err)
|
||||
}
|
||||
|
||||
if err := helpers.AreAllOneOf(settings.Regions, filterChoices.Regions); err != nil {
|
||||
return fmt.Errorf("%w: %s", ErrRegionNotValid, err)
|
||||
err = validate.AreAllOneOfCaseInsensitive(settings.Regions, filterChoices.Regions)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %w", ErrRegionNotValid, err)
|
||||
}
|
||||
|
||||
if err := helpers.AreAllOneOf(settings.Cities, filterChoices.Cities); err != nil {
|
||||
return fmt.Errorf("%w: %s", ErrCityNotValid, err)
|
||||
err = validate.AreAllOneOfCaseInsensitive(settings.Cities, filterChoices.Cities)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %w", ErrCityNotValid, err)
|
||||
}
|
||||
|
||||
if err := helpers.AreAllOneOf(settings.ISPs, filterChoices.ISPs); err != nil {
|
||||
return fmt.Errorf("%w: %s", ErrISPNotValid, err)
|
||||
err = validate.AreAllOneOfCaseInsensitive(settings.ISPs, filterChoices.ISPs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %w", ErrISPNotValid, err)
|
||||
}
|
||||
|
||||
if err := helpers.AreAllOneOf(settings.Hostnames, filterChoices.Hostnames); err != nil {
|
||||
return fmt.Errorf("%w: %s", ErrHostnameNotValid, err)
|
||||
err = validate.AreAllOneOfCaseInsensitive(settings.Hostnames, filterChoices.Hostnames)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %w", ErrHostnameNotValid, err)
|
||||
}
|
||||
|
||||
if err := helpers.AreAllOneOf(settings.Names, filterChoices.Names); err != nil {
|
||||
return fmt.Errorf("%w: %s", ErrNameNotValid, err)
|
||||
err = validate.AreAllOneOfCaseInsensitive(settings.Names, filterChoices.Names)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %w", ErrNameNotValid, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -203,70 +216,70 @@ func (ss *ServerSelection) copy() (copied ServerSelection) {
|
||||
return ServerSelection{
|
||||
VPN: ss.VPN,
|
||||
TargetIP: ss.TargetIP,
|
||||
Countries: helpers.CopySlice(ss.Countries),
|
||||
Regions: helpers.CopySlice(ss.Regions),
|
||||
Cities: helpers.CopySlice(ss.Cities),
|
||||
ISPs: helpers.CopySlice(ss.ISPs),
|
||||
Hostnames: helpers.CopySlice(ss.Hostnames),
|
||||
Names: helpers.CopySlice(ss.Names),
|
||||
Numbers: helpers.CopySlice(ss.Numbers),
|
||||
OwnedOnly: helpers.CopyPointer(ss.OwnedOnly),
|
||||
FreeOnly: helpers.CopyPointer(ss.FreeOnly),
|
||||
PremiumOnly: helpers.CopyPointer(ss.PremiumOnly),
|
||||
StreamOnly: helpers.CopyPointer(ss.StreamOnly),
|
||||
MultiHopOnly: helpers.CopyPointer(ss.MultiHopOnly),
|
||||
Countries: gosettings.CopySlice(ss.Countries),
|
||||
Regions: gosettings.CopySlice(ss.Regions),
|
||||
Cities: gosettings.CopySlice(ss.Cities),
|
||||
ISPs: gosettings.CopySlice(ss.ISPs),
|
||||
Hostnames: gosettings.CopySlice(ss.Hostnames),
|
||||
Names: gosettings.CopySlice(ss.Names),
|
||||
Numbers: gosettings.CopySlice(ss.Numbers),
|
||||
OwnedOnly: gosettings.CopyPointer(ss.OwnedOnly),
|
||||
FreeOnly: gosettings.CopyPointer(ss.FreeOnly),
|
||||
PremiumOnly: gosettings.CopyPointer(ss.PremiumOnly),
|
||||
StreamOnly: gosettings.CopyPointer(ss.StreamOnly),
|
||||
MultiHopOnly: gosettings.CopyPointer(ss.MultiHopOnly),
|
||||
OpenVPN: ss.OpenVPN.copy(),
|
||||
Wireguard: ss.Wireguard.copy(),
|
||||
}
|
||||
}
|
||||
|
||||
func (ss *ServerSelection) mergeWith(other ServerSelection) {
|
||||
ss.VPN = helpers.MergeWithString(ss.VPN, other.VPN)
|
||||
ss.TargetIP = helpers.MergeWithIP(ss.TargetIP, other.TargetIP)
|
||||
ss.Countries = helpers.MergeSlices(ss.Countries, other.Countries)
|
||||
ss.Regions = helpers.MergeSlices(ss.Regions, other.Regions)
|
||||
ss.Cities = helpers.MergeSlices(ss.Cities, other.Cities)
|
||||
ss.ISPs = helpers.MergeSlices(ss.ISPs, other.ISPs)
|
||||
ss.Hostnames = helpers.MergeSlices(ss.Hostnames, other.Hostnames)
|
||||
ss.Names = helpers.MergeSlices(ss.Names, other.Names)
|
||||
ss.Numbers = helpers.MergeSlices(ss.Numbers, other.Numbers)
|
||||
ss.OwnedOnly = helpers.MergeWithPointer(ss.OwnedOnly, other.OwnedOnly)
|
||||
ss.FreeOnly = helpers.MergeWithPointer(ss.FreeOnly, other.FreeOnly)
|
||||
ss.PremiumOnly = helpers.MergeWithPointer(ss.PremiumOnly, other.PremiumOnly)
|
||||
ss.StreamOnly = helpers.MergeWithPointer(ss.StreamOnly, other.StreamOnly)
|
||||
ss.MultiHopOnly = helpers.MergeWithPointer(ss.MultiHopOnly, other.MultiHopOnly)
|
||||
ss.VPN = gosettings.MergeWithString(ss.VPN, other.VPN)
|
||||
ss.TargetIP = gosettings.MergeWithValidator(ss.TargetIP, other.TargetIP)
|
||||
ss.Countries = gosettings.MergeWithSlice(ss.Countries, other.Countries)
|
||||
ss.Regions = gosettings.MergeWithSlice(ss.Regions, other.Regions)
|
||||
ss.Cities = gosettings.MergeWithSlice(ss.Cities, other.Cities)
|
||||
ss.ISPs = gosettings.MergeWithSlice(ss.ISPs, other.ISPs)
|
||||
ss.Hostnames = gosettings.MergeWithSlice(ss.Hostnames, other.Hostnames)
|
||||
ss.Names = gosettings.MergeWithSlice(ss.Names, other.Names)
|
||||
ss.Numbers = gosettings.MergeWithSlice(ss.Numbers, other.Numbers)
|
||||
ss.OwnedOnly = gosettings.MergeWithPointer(ss.OwnedOnly, other.OwnedOnly)
|
||||
ss.FreeOnly = gosettings.MergeWithPointer(ss.FreeOnly, other.FreeOnly)
|
||||
ss.PremiumOnly = gosettings.MergeWithPointer(ss.PremiumOnly, other.PremiumOnly)
|
||||
ss.StreamOnly = gosettings.MergeWithPointer(ss.StreamOnly, other.StreamOnly)
|
||||
ss.MultiHopOnly = gosettings.MergeWithPointer(ss.MultiHopOnly, other.MultiHopOnly)
|
||||
|
||||
ss.OpenVPN.mergeWith(other.OpenVPN)
|
||||
ss.Wireguard.mergeWith(other.Wireguard)
|
||||
}
|
||||
|
||||
func (ss *ServerSelection) overrideWith(other ServerSelection) {
|
||||
ss.VPN = helpers.OverrideWithString(ss.VPN, other.VPN)
|
||||
ss.TargetIP = helpers.OverrideWithIP(ss.TargetIP, other.TargetIP)
|
||||
ss.Countries = helpers.OverrideWithSlice(ss.Countries, other.Countries)
|
||||
ss.Regions = helpers.OverrideWithSlice(ss.Regions, other.Regions)
|
||||
ss.Cities = helpers.OverrideWithSlice(ss.Cities, other.Cities)
|
||||
ss.ISPs = helpers.OverrideWithSlice(ss.ISPs, other.ISPs)
|
||||
ss.Hostnames = helpers.OverrideWithSlice(ss.Hostnames, other.Hostnames)
|
||||
ss.Names = helpers.OverrideWithSlice(ss.Names, other.Names)
|
||||
ss.Numbers = helpers.OverrideWithSlice(ss.Numbers, other.Numbers)
|
||||
ss.OwnedOnly = helpers.OverrideWithPointer(ss.OwnedOnly, other.OwnedOnly)
|
||||
ss.FreeOnly = helpers.OverrideWithPointer(ss.FreeOnly, other.FreeOnly)
|
||||
ss.PremiumOnly = helpers.OverrideWithPointer(ss.PremiumOnly, other.PremiumOnly)
|
||||
ss.StreamOnly = helpers.OverrideWithPointer(ss.StreamOnly, other.StreamOnly)
|
||||
ss.MultiHopOnly = helpers.OverrideWithPointer(ss.MultiHopOnly, other.MultiHopOnly)
|
||||
ss.VPN = gosettings.OverrideWithString(ss.VPN, other.VPN)
|
||||
ss.TargetIP = gosettings.OverrideWithValidator(ss.TargetIP, other.TargetIP)
|
||||
ss.Countries = gosettings.OverrideWithSlice(ss.Countries, other.Countries)
|
||||
ss.Regions = gosettings.OverrideWithSlice(ss.Regions, other.Regions)
|
||||
ss.Cities = gosettings.OverrideWithSlice(ss.Cities, other.Cities)
|
||||
ss.ISPs = gosettings.OverrideWithSlice(ss.ISPs, other.ISPs)
|
||||
ss.Hostnames = gosettings.OverrideWithSlice(ss.Hostnames, other.Hostnames)
|
||||
ss.Names = gosettings.OverrideWithSlice(ss.Names, other.Names)
|
||||
ss.Numbers = gosettings.OverrideWithSlice(ss.Numbers, other.Numbers)
|
||||
ss.OwnedOnly = gosettings.OverrideWithPointer(ss.OwnedOnly, other.OwnedOnly)
|
||||
ss.FreeOnly = gosettings.OverrideWithPointer(ss.FreeOnly, other.FreeOnly)
|
||||
ss.PremiumOnly = gosettings.OverrideWithPointer(ss.PremiumOnly, other.PremiumOnly)
|
||||
ss.StreamOnly = gosettings.OverrideWithPointer(ss.StreamOnly, other.StreamOnly)
|
||||
ss.MultiHopOnly = gosettings.OverrideWithPointer(ss.MultiHopOnly, other.MultiHopOnly)
|
||||
ss.OpenVPN.overrideWith(other.OpenVPN)
|
||||
ss.Wireguard.overrideWith(other.Wireguard)
|
||||
}
|
||||
|
||||
func (ss *ServerSelection) setDefaults(vpnProvider string) {
|
||||
ss.VPN = helpers.DefaultString(ss.VPN, vpn.OpenVPN)
|
||||
ss.TargetIP = helpers.DefaultIP(ss.TargetIP, netip.IPv4Unspecified())
|
||||
ss.OwnedOnly = helpers.DefaultPointer(ss.OwnedOnly, false)
|
||||
ss.FreeOnly = helpers.DefaultPointer(ss.FreeOnly, false)
|
||||
ss.PremiumOnly = helpers.DefaultPointer(ss.PremiumOnly, false)
|
||||
ss.StreamOnly = helpers.DefaultPointer(ss.StreamOnly, false)
|
||||
ss.MultiHopOnly = helpers.DefaultPointer(ss.MultiHopOnly, false)
|
||||
ss.VPN = gosettings.DefaultString(ss.VPN, vpn.OpenVPN)
|
||||
ss.TargetIP = gosettings.DefaultValidator(ss.TargetIP, netip.IPv4Unspecified())
|
||||
ss.OwnedOnly = gosettings.DefaultPointer(ss.OwnedOnly, false)
|
||||
ss.FreeOnly = gosettings.DefaultPointer(ss.FreeOnly, false)
|
||||
ss.PremiumOnly = gosettings.DefaultPointer(ss.PremiumOnly, false)
|
||||
ss.StreamOnly = gosettings.DefaultPointer(ss.StreamOnly, false)
|
||||
ss.MultiHopOnly = gosettings.DefaultPointer(ss.MultiHopOnly, false)
|
||||
ss.OpenVPN.setDefaults(vpnProvider)
|
||||
ss.Wireguard.setDefaults()
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ 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"
|
||||
@@ -170,27 +169,14 @@ func (s Settings) Warnings() (warnings []string) {
|
||||
|
||||
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.")
|
||||
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.")
|
||||
}
|
||||
|
||||
return warnings
|
||||
|
||||
@@ -38,8 +38,8 @@ func Test_Settings_String(t *testing.T) {
|
||||
| ├── Run OpenVPN as: root
|
||||
| └── Verbosity level: 1
|
||||
├── DNS settings:
|
||||
| ├── DNS server address to use: 127.0.0.1
|
||||
| ├── Keep existing nameserver(s): no
|
||||
| ├── DNS server address to use: 127.0.0.1
|
||||
| └── DNS over TLS settings:
|
||||
| ├── Enabled: yes
|
||||
| ├── Update period: every 24h0m0s
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package settings
|
||||
|
||||
import (
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||
"github.com/qdm12/gosettings"
|
||||
"github.com/qdm12/gotree"
|
||||
"github.com/qdm12/ss-server/pkg/tcpudp"
|
||||
)
|
||||
@@ -21,7 +21,7 @@ func (s Shadowsocks) validate() (err error) {
|
||||
|
||||
func (s *Shadowsocks) copy() (copied Shadowsocks) {
|
||||
return Shadowsocks{
|
||||
Enabled: helpers.CopyPointer(s.Enabled),
|
||||
Enabled: gosettings.CopyPointer(s.Enabled),
|
||||
Settings: s.Settings.Copy(),
|
||||
}
|
||||
}
|
||||
@@ -29,20 +29,20 @@ func (s *Shadowsocks) copy() (copied Shadowsocks) {
|
||||
// mergeWith merges the other settings into any
|
||||
// unset field of the receiver settings object.
|
||||
func (s *Shadowsocks) mergeWith(other Shadowsocks) {
|
||||
s.Enabled = helpers.MergeWithPointer(s.Enabled, other.Enabled)
|
||||
s.Settings.MergeWith(other.Settings)
|
||||
s.Enabled = gosettings.MergeWithPointer(s.Enabled, other.Enabled)
|
||||
s.Settings = s.Settings.MergeWith(other.Settings)
|
||||
}
|
||||
|
||||
// overrideWith overrides fields of the receiver
|
||||
// settings object with any field set in the other
|
||||
// settings.
|
||||
func (s *Shadowsocks) overrideWith(other Shadowsocks) {
|
||||
s.Enabled = helpers.OverrideWithPointer(s.Enabled, other.Enabled)
|
||||
s.Enabled = gosettings.OverrideWithPointer(s.Enabled, other.Enabled)
|
||||
s.Settings.OverrideWith(other.Settings)
|
||||
}
|
||||
|
||||
func (s *Shadowsocks) setDefaults() {
|
||||
s.Enabled = helpers.DefaultPointer(s.Enabled, false)
|
||||
s.Enabled = gosettings.DefaultPointer(s.Enabled, false)
|
||||
s.Settings.SetDefaults()
|
||||
}
|
||||
|
||||
@@ -53,16 +53,16 @@ func (s Shadowsocks) String() string {
|
||||
func (s Shadowsocks) toLinesNode() (node *gotree.Node) {
|
||||
node = gotree.New("Shadowsocks server settings:")
|
||||
|
||||
node.Appendf("Enabled: %s", helpers.BoolPtrToYesNo(s.Enabled))
|
||||
node.Appendf("Enabled: %s", gosettings.BoolToYesNo(s.Enabled))
|
||||
if !*s.Enabled {
|
||||
return node
|
||||
}
|
||||
|
||||
// TODO have ToLinesNode in qdm12/ss-server
|
||||
node.Appendf("Listening address: %s", s.Address)
|
||||
node.Appendf("Listening address: %s", *s.Address)
|
||||
node.Appendf("Cipher: %s", s.CipherName)
|
||||
node.Appendf("Password: %s", helpers.ObfuscatePassword(*s.Password))
|
||||
node.Appendf("Log addresses: %s", helpers.BoolPtrToYesNo(s.LogAddresses))
|
||||
node.Appendf("Password: %s", gosettings.ObfuscateKey(*s.Password))
|
||||
node.Appendf("Log addresses: %s", gosettings.BoolToYesNo(s.LogAddresses))
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package settings
|
||||
|
||||
import (
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||
"github.com/qdm12/gosettings"
|
||||
"github.com/qdm12/gotree"
|
||||
)
|
||||
|
||||
@@ -19,28 +19,28 @@ func (s System) validate() (err error) {
|
||||
|
||||
func (s *System) copy() (copied System) {
|
||||
return System{
|
||||
PUID: helpers.CopyPointer(s.PUID),
|
||||
PGID: helpers.CopyPointer(s.PGID),
|
||||
PUID: gosettings.CopyPointer(s.PUID),
|
||||
PGID: gosettings.CopyPointer(s.PGID),
|
||||
Timezone: s.Timezone,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *System) mergeWith(other System) {
|
||||
s.PUID = helpers.MergeWithPointer(s.PUID, other.PUID)
|
||||
s.PGID = helpers.MergeWithPointer(s.PGID, other.PGID)
|
||||
s.Timezone = helpers.MergeWithString(s.Timezone, other.Timezone)
|
||||
s.PUID = gosettings.MergeWithPointer(s.PUID, other.PUID)
|
||||
s.PGID = gosettings.MergeWithPointer(s.PGID, other.PGID)
|
||||
s.Timezone = gosettings.MergeWithString(s.Timezone, other.Timezone)
|
||||
}
|
||||
|
||||
func (s *System) overrideWith(other System) {
|
||||
s.PUID = helpers.OverrideWithPointer(s.PUID, other.PUID)
|
||||
s.PGID = helpers.OverrideWithPointer(s.PGID, other.PGID)
|
||||
s.Timezone = helpers.OverrideWithString(s.Timezone, other.Timezone)
|
||||
s.PUID = gosettings.OverrideWithPointer(s.PUID, other.PUID)
|
||||
s.PGID = gosettings.OverrideWithPointer(s.PGID, other.PGID)
|
||||
s.Timezone = gosettings.OverrideWithString(s.Timezone, other.Timezone)
|
||||
}
|
||||
|
||||
func (s *System) setDefaults() {
|
||||
const defaultID = 1000
|
||||
s.PUID = helpers.DefaultPointer(s.PUID, defaultID)
|
||||
s.PGID = helpers.DefaultPointer(s.PGID, defaultID)
|
||||
s.PUID = gosettings.DefaultPointer(s.PUID, defaultID)
|
||||
s.PGID = gosettings.DefaultPointer(s.PGID, defaultID)
|
||||
}
|
||||
|
||||
func (s System) String() string {
|
||||
|
||||
@@ -7,20 +7,20 @@ import (
|
||||
|
||||
"github.com/qdm12/dns/pkg/provider"
|
||||
"github.com/qdm12/dns/pkg/unbound"
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||
"github.com/qdm12/gosettings"
|
||||
"github.com/qdm12/gotree"
|
||||
)
|
||||
|
||||
// Unbound is settings for the Unbound program.
|
||||
type Unbound struct {
|
||||
Providers []string
|
||||
Caching *bool
|
||||
IPv6 *bool
|
||||
VerbosityLevel *uint8
|
||||
VerbosityDetailsLevel *uint8
|
||||
ValidationLogLevel *uint8
|
||||
Username string
|
||||
Allowed []netip.Prefix
|
||||
Providers []string `json:"providers"`
|
||||
Caching *bool `json:"caching"`
|
||||
IPv6 *bool `json:"ipv6"`
|
||||
VerbosityLevel *uint8 `json:"verbosity_level"`
|
||||
VerbosityDetailsLevel *uint8 `json:"verbosity_details_level"`
|
||||
ValidationLogLevel *uint8 `json:"validation_log_level"`
|
||||
Username string `json:"username"`
|
||||
Allowed []netip.Prefix `json:"allowed"`
|
||||
}
|
||||
|
||||
func (u *Unbound) setDefaults() {
|
||||
@@ -30,17 +30,17 @@ func (u *Unbound) setDefaults() {
|
||||
}
|
||||
}
|
||||
|
||||
u.Caching = helpers.DefaultPointer(u.Caching, true)
|
||||
u.IPv6 = helpers.DefaultPointer(u.IPv6, false)
|
||||
u.Caching = gosettings.DefaultPointer(u.Caching, true)
|
||||
u.IPv6 = gosettings.DefaultPointer(u.IPv6, false)
|
||||
|
||||
const defaultVerbosityLevel = 1
|
||||
u.VerbosityLevel = helpers.DefaultPointer(u.VerbosityLevel, defaultVerbosityLevel)
|
||||
u.VerbosityLevel = gosettings.DefaultPointer(u.VerbosityLevel, defaultVerbosityLevel)
|
||||
|
||||
const defaultVerbosityDetailsLevel = 0
|
||||
u.VerbosityDetailsLevel = helpers.DefaultPointer(u.VerbosityDetailsLevel, defaultVerbosityDetailsLevel)
|
||||
u.VerbosityDetailsLevel = gosettings.DefaultPointer(u.VerbosityDetailsLevel, defaultVerbosityDetailsLevel)
|
||||
|
||||
const defaultValidationLogLevel = 0
|
||||
u.ValidationLogLevel = helpers.DefaultPointer(u.ValidationLogLevel, defaultValidationLogLevel)
|
||||
u.ValidationLogLevel = gosettings.DefaultPointer(u.ValidationLogLevel, defaultValidationLogLevel)
|
||||
|
||||
if u.Allowed == nil {
|
||||
u.Allowed = []netip.Prefix{
|
||||
@@ -49,7 +49,7 @@ func (u *Unbound) setDefaults() {
|
||||
}
|
||||
}
|
||||
|
||||
u.Username = helpers.DefaultString(u.Username, "root")
|
||||
u.Username = gosettings.DefaultString(u.Username, "root")
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -94,37 +94,37 @@ func (u Unbound) validate() (err error) {
|
||||
|
||||
func (u Unbound) copy() (copied Unbound) {
|
||||
return Unbound{
|
||||
Providers: helpers.CopySlice(u.Providers),
|
||||
Caching: helpers.CopyPointer(u.Caching),
|
||||
IPv6: helpers.CopyPointer(u.IPv6),
|
||||
VerbosityLevel: helpers.CopyPointer(u.VerbosityLevel),
|
||||
VerbosityDetailsLevel: helpers.CopyPointer(u.VerbosityDetailsLevel),
|
||||
ValidationLogLevel: helpers.CopyPointer(u.ValidationLogLevel),
|
||||
Providers: gosettings.CopySlice(u.Providers),
|
||||
Caching: gosettings.CopyPointer(u.Caching),
|
||||
IPv6: gosettings.CopyPointer(u.IPv6),
|
||||
VerbosityLevel: gosettings.CopyPointer(u.VerbosityLevel),
|
||||
VerbosityDetailsLevel: gosettings.CopyPointer(u.VerbosityDetailsLevel),
|
||||
ValidationLogLevel: gosettings.CopyPointer(u.ValidationLogLevel),
|
||||
Username: u.Username,
|
||||
Allowed: helpers.CopySlice(u.Allowed),
|
||||
Allowed: gosettings.CopySlice(u.Allowed),
|
||||
}
|
||||
}
|
||||
|
||||
func (u *Unbound) mergeWith(other Unbound) {
|
||||
u.Providers = helpers.MergeSlices(u.Providers, other.Providers)
|
||||
u.Caching = helpers.MergeWithPointer(u.Caching, other.Caching)
|
||||
u.IPv6 = helpers.MergeWithPointer(u.IPv6, other.IPv6)
|
||||
u.VerbosityLevel = helpers.MergeWithPointer(u.VerbosityLevel, other.VerbosityLevel)
|
||||
u.VerbosityDetailsLevel = helpers.MergeWithPointer(u.VerbosityDetailsLevel, other.VerbosityDetailsLevel)
|
||||
u.ValidationLogLevel = helpers.MergeWithPointer(u.ValidationLogLevel, other.ValidationLogLevel)
|
||||
u.Username = helpers.MergeWithString(u.Username, other.Username)
|
||||
u.Allowed = helpers.MergeSlices(u.Allowed, other.Allowed)
|
||||
u.Providers = gosettings.MergeWithSlice(u.Providers, other.Providers)
|
||||
u.Caching = gosettings.MergeWithPointer(u.Caching, other.Caching)
|
||||
u.IPv6 = gosettings.MergeWithPointer(u.IPv6, other.IPv6)
|
||||
u.VerbosityLevel = gosettings.MergeWithPointer(u.VerbosityLevel, other.VerbosityLevel)
|
||||
u.VerbosityDetailsLevel = gosettings.MergeWithPointer(u.VerbosityDetailsLevel, other.VerbosityDetailsLevel)
|
||||
u.ValidationLogLevel = gosettings.MergeWithPointer(u.ValidationLogLevel, other.ValidationLogLevel)
|
||||
u.Username = gosettings.MergeWithString(u.Username, other.Username)
|
||||
u.Allowed = gosettings.MergeWithSlice(u.Allowed, other.Allowed)
|
||||
}
|
||||
|
||||
func (u *Unbound) overrideWith(other Unbound) {
|
||||
u.Providers = helpers.OverrideWithSlice(u.Providers, other.Providers)
|
||||
u.Caching = helpers.OverrideWithPointer(u.Caching, other.Caching)
|
||||
u.IPv6 = helpers.OverrideWithPointer(u.IPv6, other.IPv6)
|
||||
u.VerbosityLevel = helpers.OverrideWithPointer(u.VerbosityLevel, other.VerbosityLevel)
|
||||
u.VerbosityDetailsLevel = helpers.OverrideWithPointer(u.VerbosityDetailsLevel, other.VerbosityDetailsLevel)
|
||||
u.ValidationLogLevel = helpers.OverrideWithPointer(u.ValidationLogLevel, other.ValidationLogLevel)
|
||||
u.Username = helpers.OverrideWithString(u.Username, other.Username)
|
||||
u.Allowed = helpers.OverrideWithSlice(u.Allowed, other.Allowed)
|
||||
u.Providers = gosettings.OverrideWithSlice(u.Providers, other.Providers)
|
||||
u.Caching = gosettings.OverrideWithPointer(u.Caching, other.Caching)
|
||||
u.IPv6 = gosettings.OverrideWithPointer(u.IPv6, other.IPv6)
|
||||
u.VerbosityLevel = gosettings.OverrideWithPointer(u.VerbosityLevel, other.VerbosityLevel)
|
||||
u.VerbosityDetailsLevel = gosettings.OverrideWithPointer(u.VerbosityDetailsLevel, other.VerbosityDetailsLevel)
|
||||
u.ValidationLogLevel = gosettings.OverrideWithPointer(u.ValidationLogLevel, other.ValidationLogLevel)
|
||||
u.Username = gosettings.OverrideWithString(u.Username, other.Username)
|
||||
u.Allowed = gosettings.OverrideWithSlice(u.Allowed, other.Allowed)
|
||||
}
|
||||
|
||||
func (u Unbound) ToUnboundFormat() (settings unbound.Settings, err error) {
|
||||
@@ -186,8 +186,8 @@ func (u Unbound) toLinesNode() (node *gotree.Node) {
|
||||
authServers.Appendf(provider)
|
||||
}
|
||||
|
||||
node.Appendf("Caching: %s", helpers.BoolPtrToYesNo(u.Caching))
|
||||
node.Appendf("IPv6: %s", helpers.BoolPtrToYesNo(u.IPv6))
|
||||
node.Appendf("Caching: %s", gosettings.BoolToYesNo(u.Caching))
|
||||
node.Appendf("IPv6: %s", gosettings.BoolToYesNo(u.IPv6))
|
||||
node.Appendf("Verbosity level: %d", *u.VerbosityLevel)
|
||||
node.Appendf("Verbosity details level: %d", *u.VerbosityDetailsLevel)
|
||||
node.Appendf("Validation log level: %d", *u.ValidationLogLevel)
|
||||
|
||||
@@ -29,9 +29,9 @@ func Test_Unbound_JSON(t *testing.T) {
|
||||
b, err := json.Marshal(settings)
|
||||
require.NoError(t, err)
|
||||
|
||||
const expected = `{"Providers":["cloudflare"],"Caching":true,"IPv6":false,` +
|
||||
`"VerbosityLevel":1,"VerbosityDetailsLevel":null,"ValidationLogLevel":0,` +
|
||||
`"Username":"user","Allowed":["0.0.0.0/0","::/0"]}`
|
||||
const expected = `{"providers":["cloudflare"],"caching":true,"ipv6":false,` +
|
||||
`"verbosity_level":1,"verbosity_details_level":null,"validation_log_level":0,` +
|
||||
`"username":"user","allowed":["0.0.0.0/0","::/0"]}`
|
||||
|
||||
assert.Equal(t, expected, string(b))
|
||||
|
||||
|
||||
@@ -5,8 +5,9 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||
"github.com/qdm12/gosettings"
|
||||
"github.com/qdm12/gosettings/validate"
|
||||
"github.com/qdm12/gotree"
|
||||
)
|
||||
|
||||
@@ -45,16 +46,9 @@ func (u Updater) Validate() (err error) {
|
||||
|
||||
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: %q can only be one of %s",
|
||||
ErrVPNProviderNameNotValid, provider, helpers.ChoicesOrString(validProviders))
|
||||
err = validate.IsOneOf(provider, validProviders...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %w", ErrVPNProviderNameNotValid, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,35 +57,35 @@ func (u Updater) Validate() (err error) {
|
||||
|
||||
func (u *Updater) copy() (copied Updater) {
|
||||
return Updater{
|
||||
Period: helpers.CopyPointer(u.Period),
|
||||
Period: gosettings.CopyPointer(u.Period),
|
||||
DNSAddress: u.DNSAddress,
|
||||
MinRatio: u.MinRatio,
|
||||
Providers: helpers.CopySlice(u.Providers),
|
||||
Providers: gosettings.CopySlice(u.Providers),
|
||||
}
|
||||
}
|
||||
|
||||
// mergeWith merges the other settings into any
|
||||
// unset field of the receiver settings object.
|
||||
func (u *Updater) mergeWith(other Updater) {
|
||||
u.Period = helpers.MergeWithPointer(u.Period, other.Period)
|
||||
u.DNSAddress = helpers.MergeWithString(u.DNSAddress, other.DNSAddress)
|
||||
u.MinRatio = helpers.MergeWithNumber(u.MinRatio, other.MinRatio)
|
||||
u.Providers = helpers.MergeSlices(u.Providers, other.Providers)
|
||||
u.Period = gosettings.MergeWithPointer(u.Period, other.Period)
|
||||
u.DNSAddress = gosettings.MergeWithString(u.DNSAddress, other.DNSAddress)
|
||||
u.MinRatio = gosettings.MergeWithNumber(u.MinRatio, other.MinRatio)
|
||||
u.Providers = gosettings.MergeWithSlice(u.Providers, other.Providers)
|
||||
}
|
||||
|
||||
// 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.OverrideWithPointer(u.Period, other.Period)
|
||||
u.DNSAddress = helpers.OverrideWithString(u.DNSAddress, other.DNSAddress)
|
||||
u.MinRatio = helpers.OverrideWithNumber(u.MinRatio, other.MinRatio)
|
||||
u.Providers = helpers.OverrideWithSlice(u.Providers, other.Providers)
|
||||
u.Period = gosettings.OverrideWithPointer(u.Period, other.Period)
|
||||
u.DNSAddress = gosettings.OverrideWithString(u.DNSAddress, other.DNSAddress)
|
||||
u.MinRatio = gosettings.OverrideWithNumber(u.MinRatio, other.MinRatio)
|
||||
u.Providers = gosettings.OverrideWithSlice(u.Providers, other.Providers)
|
||||
}
|
||||
|
||||
func (u *Updater) SetDefaults(vpnProvider string) {
|
||||
u.Period = helpers.DefaultPointer(u.Period, 0)
|
||||
u.DNSAddress = helpers.DefaultString(u.DNSAddress, "1.1.1.1:53")
|
||||
u.Period = gosettings.DefaultPointer(u.Period, 0)
|
||||
u.DNSAddress = gosettings.DefaultString(u.DNSAddress, "1.1.1.1:53")
|
||||
|
||||
if u.MinRatio == 0 {
|
||||
const defaultMinRatio = 0.8
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package settings
|
||||
|
||||
import (
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||
"github.com/qdm12/gosettings"
|
||||
"github.com/qdm12/gotree"
|
||||
)
|
||||
|
||||
@@ -19,25 +19,25 @@ func (v Version) validate() (err error) {
|
||||
|
||||
func (v *Version) copy() (copied Version) {
|
||||
return Version{
|
||||
Enabled: helpers.CopyPointer(v.Enabled),
|
||||
Enabled: gosettings.CopyPointer(v.Enabled),
|
||||
}
|
||||
}
|
||||
|
||||
// mergeWith merges the other settings into any
|
||||
// unset field of the receiver settings object.
|
||||
func (v *Version) mergeWith(other Version) {
|
||||
v.Enabled = helpers.MergeWithPointer(v.Enabled, other.Enabled)
|
||||
v.Enabled = gosettings.MergeWithPointer(v.Enabled, other.Enabled)
|
||||
}
|
||||
|
||||
// overrideWith overrides fields of the receiver
|
||||
// settings object with any field set in the other
|
||||
// settings.
|
||||
func (v *Version) overrideWith(other Version) {
|
||||
v.Enabled = helpers.OverrideWithPointer(v.Enabled, other.Enabled)
|
||||
v.Enabled = gosettings.OverrideWithPointer(v.Enabled, other.Enabled)
|
||||
}
|
||||
|
||||
func (v *Version) setDefaults() {
|
||||
v.Enabled = helpers.DefaultPointer(v.Enabled, true)
|
||||
v.Enabled = gosettings.DefaultPointer(v.Enabled, true)
|
||||
}
|
||||
|
||||
func (v Version) String() string {
|
||||
@@ -47,7 +47,7 @@ func (v Version) String() string {
|
||||
func (v Version) toLinesNode() (node *gotree.Node) {
|
||||
node = gotree.New("Version settings:")
|
||||
|
||||
node.Appendf("Enabled: %s", helpers.BoolPtrToYesNo(v.Enabled))
|
||||
node.Appendf("Enabled: %s", gosettings.BoolToYesNo(v.Enabled))
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
@@ -2,10 +2,10 @@ package settings
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||
"github.com/qdm12/gluetun/internal/constants/vpn"
|
||||
"github.com/qdm12/gosettings"
|
||||
"github.com/qdm12/gosettings/validate"
|
||||
"github.com/qdm12/gotree"
|
||||
)
|
||||
|
||||
@@ -13,19 +13,18 @@ type VPN struct {
|
||||
// Type is the VPN type and can only be
|
||||
// 'openvpn' or 'wireguard'. It cannot be the
|
||||
// empty string in the internal state.
|
||||
Type string
|
||||
Provider Provider
|
||||
OpenVPN OpenVPN
|
||||
Wireguard Wireguard
|
||||
Type string `json:"type"`
|
||||
Provider Provider `json:"provider"`
|
||||
OpenVPN OpenVPN `json:"openvpn"`
|
||||
Wireguard Wireguard `json:"wireguard"`
|
||||
}
|
||||
|
||||
// TODO v4 remove pointer for receiver (because of Surfshark).
|
||||
func (v *VPN) Validate(storage Storage, ipv6Supported bool) (err error) {
|
||||
// Validate Type
|
||||
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, ", "))
|
||||
if err = validate.IsOneOf(v.Type, validVPNTypes...); err != nil {
|
||||
return fmt.Errorf("%w: %w", ErrVPNTypeNotValid, err)
|
||||
}
|
||||
|
||||
err = v.Provider.validate(v.Type, storage)
|
||||
@@ -58,24 +57,24 @@ func (v *VPN) Copy() (copied VPN) {
|
||||
}
|
||||
|
||||
func (v *VPN) mergeWith(other VPN) {
|
||||
v.Type = helpers.MergeWithString(v.Type, other.Type)
|
||||
v.Type = gosettings.MergeWithString(v.Type, other.Type)
|
||||
v.Provider.mergeWith(other.Provider)
|
||||
v.OpenVPN.mergeWith(other.OpenVPN)
|
||||
v.Wireguard.mergeWith(other.Wireguard)
|
||||
}
|
||||
|
||||
func (v *VPN) OverrideWith(other VPN) {
|
||||
v.Type = helpers.OverrideWithString(v.Type, other.Type)
|
||||
v.Type = gosettings.OverrideWithString(v.Type, other.Type)
|
||||
v.Provider.overrideWith(other.Provider)
|
||||
v.OpenVPN.overrideWith(other.OpenVPN)
|
||||
v.Wireguard.overrideWith(other.Wireguard)
|
||||
}
|
||||
|
||||
func (v *VPN) setDefaults() {
|
||||
v.Type = helpers.DefaultString(v.Type, vpn.OpenVPN)
|
||||
v.Type = gosettings.DefaultString(v.Type, vpn.OpenVPN)
|
||||
v.Provider.setDefaults()
|
||||
v.OpenVPN.setDefaults(*v.Provider.Name)
|
||||
v.Wireguard.setDefaults()
|
||||
v.Wireguard.setDefaults(*v.Provider.Name)
|
||||
}
|
||||
|
||||
func (v VPN) String() string {
|
||||
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||
"github.com/qdm12/gosettings"
|
||||
"github.com/qdm12/gosettings/validate"
|
||||
"github.com/qdm12/gotree"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
)
|
||||
@@ -15,23 +17,33 @@ import (
|
||||
type Wireguard struct {
|
||||
// PrivateKey is the Wireguard client peer private key.
|
||||
// It cannot be nil in the internal state.
|
||||
PrivateKey *string
|
||||
PrivateKey *string `json:"private_key"`
|
||||
// PreSharedKey is the Wireguard pre-shared key.
|
||||
// It can be the empty string to indicate there
|
||||
// is no pre-shared key.
|
||||
// It cannot be nil in the internal state.
|
||||
PreSharedKey *string
|
||||
PreSharedKey *string `json:"pre_shared_key"`
|
||||
// Addresses are the Wireguard interface addresses.
|
||||
Addresses []netip.Prefix
|
||||
Addresses []netip.Prefix `json:"addresses"`
|
||||
// AllowedIPs are the Wireguard allowed IPs.
|
||||
// If left unset, they default to "0.0.0.0/0"
|
||||
// and, if IPv6 is supported, "::0".
|
||||
AllowedIPs []netip.Prefix `json:"allowed_ips"`
|
||||
// Interface is the name of the Wireguard interface
|
||||
// to create. It cannot be the empty string in the
|
||||
// internal state.
|
||||
Interface string
|
||||
Interface string `json:"interface"`
|
||||
// Maximum Transmission Unit (MTU) of the Wireguard interface.
|
||||
// It cannot be zero in the internal state, and defaults to
|
||||
// 1400. Note it is not the wireguard-go MTU default of 1420
|
||||
// because this impacts bandwidth a lot on some VPN providers,
|
||||
// see https://github.com/qdm12/gluetun/issues/1650.
|
||||
MTU uint16 `json:"mtu"`
|
||||
// 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
|
||||
Implementation string `json:"implementation"`
|
||||
}
|
||||
|
||||
var regexpInterfaceName = regexp.MustCompile(`^[a-zA-Z0-9_]+$`)
|
||||
@@ -40,9 +52,11 @@ var regexpInterfaceName = regexp.MustCompile(`^[a-zA-Z0-9_]+$`)
|
||||
// It should only be ran if the VPN type chosen is Wireguard.
|
||||
func (w Wireguard) validate(vpnProvider string, ipv6Supported bool) (err error) {
|
||||
if !helpers.IsOneOf(vpnProvider,
|
||||
providers.Airvpn,
|
||||
providers.Custom,
|
||||
providers.Ivpn,
|
||||
providers.Mullvad,
|
||||
providers.Nordvpn,
|
||||
providers.Surfshark,
|
||||
providers.Windscribe,
|
||||
) {
|
||||
@@ -79,13 +93,26 @@ func (w Wireguard) validate(vpnProvider string, ipv6Supported bool) (err error)
|
||||
}
|
||||
for i, ipNet := range w.Addresses {
|
||||
if !ipNet.IsValid() {
|
||||
return fmt.Errorf("%w: for address at index %d: %s",
|
||||
ErrWireguardInterfaceAddressNotSet, i, ipNet.String())
|
||||
return fmt.Errorf("%w: for address at index %d",
|
||||
ErrWireguardInterfaceAddressNotSet, i)
|
||||
}
|
||||
|
||||
if !ipv6Supported && ipNet.Addr().Is6() {
|
||||
return fmt.Errorf("%w: address %s",
|
||||
ErrWireguardInterfaceAddressIPv6, ipNet)
|
||||
ErrWireguardInterfaceAddressIPv6, ipNet.String())
|
||||
}
|
||||
}
|
||||
|
||||
// Validate AllowedIPs
|
||||
// WARNING: do not check for IPv6 networks in the allowed IPs,
|
||||
// the wireguard code will take care to ignore it.
|
||||
if len(w.AllowedIPs) == 0 {
|
||||
return fmt.Errorf("%w", ErrWireguardAllowedIPsNotSet)
|
||||
}
|
||||
for i, allowedIP := range w.AllowedIPs {
|
||||
if !allowedIP.IsValid() {
|
||||
return fmt.Errorf("%w: for allowed ip %d of %d",
|
||||
ErrWireguardAllowedIPNotSet, i+1, len(w.AllowedIPs))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,9 +123,8 @@ func (w Wireguard) validate(vpnProvider string, ipv6Supported bool) (err error)
|
||||
}
|
||||
|
||||
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))
|
||||
if err := validate.IsOneOf(w.Implementation, validImplementations...); err != nil {
|
||||
return fmt.Errorf("%w: %w", ErrWireguardImplementationNotValid, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -106,35 +132,53 @@ func (w Wireguard) validate(vpnProvider string, ipv6Supported bool) (err error)
|
||||
|
||||
func (w *Wireguard) copy() (copied Wireguard) {
|
||||
return Wireguard{
|
||||
PrivateKey: helpers.CopyPointer(w.PrivateKey),
|
||||
PreSharedKey: helpers.CopyPointer(w.PreSharedKey),
|
||||
Addresses: helpers.CopySlice(w.Addresses),
|
||||
PrivateKey: gosettings.CopyPointer(w.PrivateKey),
|
||||
PreSharedKey: gosettings.CopyPointer(w.PreSharedKey),
|
||||
Addresses: gosettings.CopySlice(w.Addresses),
|
||||
AllowedIPs: gosettings.CopySlice(w.AllowedIPs),
|
||||
Interface: w.Interface,
|
||||
MTU: w.MTU,
|
||||
Implementation: w.Implementation,
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Wireguard) mergeWith(other Wireguard) {
|
||||
w.PrivateKey = helpers.MergeWithPointer(w.PrivateKey, other.PrivateKey)
|
||||
w.PreSharedKey = helpers.MergeWithPointer(w.PreSharedKey, other.PreSharedKey)
|
||||
w.Addresses = helpers.MergeSlices(w.Addresses, other.Addresses)
|
||||
w.Interface = helpers.MergeWithString(w.Interface, other.Interface)
|
||||
w.Implementation = helpers.MergeWithString(w.Implementation, other.Implementation)
|
||||
w.PrivateKey = gosettings.MergeWithPointer(w.PrivateKey, other.PrivateKey)
|
||||
w.PreSharedKey = gosettings.MergeWithPointer(w.PreSharedKey, other.PreSharedKey)
|
||||
w.Addresses = gosettings.MergeWithSlice(w.Addresses, other.Addresses)
|
||||
w.AllowedIPs = gosettings.MergeWithSlice(w.AllowedIPs, other.AllowedIPs)
|
||||
w.Interface = gosettings.MergeWithString(w.Interface, other.Interface)
|
||||
w.MTU = gosettings.MergeWithNumber(w.MTU, other.MTU)
|
||||
w.Implementation = gosettings.MergeWithString(w.Implementation, other.Implementation)
|
||||
}
|
||||
|
||||
func (w *Wireguard) overrideWith(other Wireguard) {
|
||||
w.PrivateKey = helpers.OverrideWithPointer(w.PrivateKey, other.PrivateKey)
|
||||
w.PreSharedKey = helpers.OverrideWithPointer(w.PreSharedKey, other.PreSharedKey)
|
||||
w.Addresses = helpers.OverrideWithSlice(w.Addresses, other.Addresses)
|
||||
w.Interface = helpers.OverrideWithString(w.Interface, other.Interface)
|
||||
w.Implementation = helpers.OverrideWithString(w.Implementation, other.Implementation)
|
||||
w.PrivateKey = gosettings.OverrideWithPointer(w.PrivateKey, other.PrivateKey)
|
||||
w.PreSharedKey = gosettings.OverrideWithPointer(w.PreSharedKey, other.PreSharedKey)
|
||||
w.Addresses = gosettings.OverrideWithSlice(w.Addresses, other.Addresses)
|
||||
w.AllowedIPs = gosettings.OverrideWithSlice(w.AllowedIPs, other.AllowedIPs)
|
||||
w.Interface = gosettings.OverrideWithString(w.Interface, other.Interface)
|
||||
w.MTU = gosettings.OverrideWithNumber(w.MTU, other.MTU)
|
||||
w.Implementation = gosettings.OverrideWithString(w.Implementation, other.Implementation)
|
||||
}
|
||||
|
||||
func (w *Wireguard) setDefaults() {
|
||||
w.PrivateKey = helpers.DefaultPointer(w.PrivateKey, "")
|
||||
w.PreSharedKey = helpers.DefaultPointer(w.PreSharedKey, "")
|
||||
w.Interface = helpers.DefaultString(w.Interface, "wg0")
|
||||
w.Implementation = helpers.DefaultString(w.Implementation, "auto")
|
||||
func (w *Wireguard) setDefaults(vpnProvider string) {
|
||||
w.PrivateKey = gosettings.DefaultPointer(w.PrivateKey, "")
|
||||
w.PreSharedKey = gosettings.DefaultPointer(w.PreSharedKey, "")
|
||||
if vpnProvider == providers.Nordvpn {
|
||||
defaultNordVPNAddress := netip.AddrFrom4([4]byte{10, 5, 0, 2})
|
||||
defaultNordVPNPrefix := netip.PrefixFrom(defaultNordVPNAddress, defaultNordVPNAddress.BitLen())
|
||||
w.Addresses = gosettings.DefaultSlice(w.Addresses, []netip.Prefix{defaultNordVPNPrefix})
|
||||
}
|
||||
defaultAllowedIPs := []netip.Prefix{
|
||||
netip.PrefixFrom(netip.IPv4Unspecified(), 0),
|
||||
netip.PrefixFrom(netip.IPv6Unspecified(), 0),
|
||||
}
|
||||
w.AllowedIPs = gosettings.DefaultSlice(w.AllowedIPs, defaultAllowedIPs)
|
||||
w.Interface = gosettings.DefaultString(w.Interface, "wg0")
|
||||
const defaultMTU = 1400
|
||||
w.MTU = gosettings.DefaultNumber(w.MTU, defaultMTU)
|
||||
w.Implementation = gosettings.DefaultString(w.Implementation, "auto")
|
||||
}
|
||||
|
||||
func (w Wireguard) String() string {
|
||||
@@ -145,12 +189,12 @@ func (w Wireguard) toLinesNode() (node *gotree.Node) {
|
||||
node = gotree.New("Wireguard settings:")
|
||||
|
||||
if *w.PrivateKey != "" {
|
||||
s := helpers.ObfuscateWireguardKey(*w.PrivateKey)
|
||||
s := gosettings.ObfuscateKey(*w.PrivateKey)
|
||||
node.Appendf("Private key: %s", s)
|
||||
}
|
||||
|
||||
if *w.PreSharedKey != "" {
|
||||
s := helpers.ObfuscateWireguardKey(*w.PreSharedKey)
|
||||
s := gosettings.ObfuscateKey(*w.PreSharedKey)
|
||||
node.Appendf("Pre-shared key: %s", s)
|
||||
}
|
||||
|
||||
@@ -159,7 +203,13 @@ func (w Wireguard) toLinesNode() (node *gotree.Node) {
|
||||
addressesNode.Appendf(address.String())
|
||||
}
|
||||
|
||||
node.Appendf("Network interface: %s", w.Interface)
|
||||
allowedIPsNode := node.Appendf("Allowed IPs:")
|
||||
for _, allowedIP := range w.AllowedIPs {
|
||||
allowedIPsNode.Appendf(allowedIP.String())
|
||||
}
|
||||
|
||||
interfaceNode := node.Appendf("Network interface: %s", w.Interface)
|
||||
interfaceNode.Appendf("MTU: %d", w.MTU)
|
||||
|
||||
if w.Implementation != "auto" {
|
||||
node.Appendf("Implementation: %s", w.Implementation)
|
||||
|
||||
@@ -4,8 +4,9 @@ import (
|
||||
"fmt"
|
||||
"net/netip"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||
"github.com/qdm12/gosettings"
|
||||
"github.com/qdm12/gosettings/validate"
|
||||
"github.com/qdm12/gotree"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
)
|
||||
@@ -15,20 +16,20 @@ type WireguardSelection struct {
|
||||
// It is only used with VPN providers generating Wireguard
|
||||
// configurations specific to each server and user.
|
||||
// To indicate it should not be used, it should be set
|
||||
// to netaddr.IPv4Unspecified(). It can never be the zero value
|
||||
// to netip.IPv4Unspecified(). It can never be the zero value
|
||||
// in the internal state.
|
||||
EndpointIP netip.Addr
|
||||
EndpointIP netip.Addr `json:"endpoint_ip"`
|
||||
// EndpointPort is a the server port to use for the VPN server.
|
||||
// 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
|
||||
// state.
|
||||
EndpointPort *uint16
|
||||
EndpointPort *uint16 `json:"endpoint_port"`
|
||||
// PublicKey is the server public key.
|
||||
// It is only used with VPN providers generating Wireguard
|
||||
// configurations specific to each server and user.
|
||||
PublicKey string
|
||||
PublicKey string `json:"public_key"`
|
||||
}
|
||||
|
||||
// Validate validates WireguardSelection settings.
|
||||
@@ -37,7 +38,7 @@ func (w WireguardSelection) validate(vpnProvider string) (err error) {
|
||||
// Validate EndpointIP
|
||||
switch vpnProvider {
|
||||
case providers.Airvpn, providers.Ivpn, providers.Mullvad,
|
||||
providers.Surfshark, providers.Windscribe:
|
||||
providers.Nordvpn, providers.Surfshark, providers.Windscribe:
|
||||
// endpoint IP addresses are baked in
|
||||
case providers.Custom:
|
||||
if !w.EndpointIP.IsValid() || w.EndpointIP.IsUnspecified() {
|
||||
@@ -54,7 +55,7 @@ func (w WireguardSelection) validate(vpnProvider string) (err error) {
|
||||
return fmt.Errorf("%w", ErrWireguardEndpointPortNotSet)
|
||||
}
|
||||
// EndpointPort cannot be set
|
||||
case providers.Surfshark:
|
||||
case providers.Surfshark, providers.Nordvpn:
|
||||
if *w.EndpointPort != 0 {
|
||||
return fmt.Errorf("%w", ErrWireguardEndpointPortSet)
|
||||
}
|
||||
@@ -76,12 +77,12 @@ func (w WireguardSelection) validate(vpnProvider string) (err error) {
|
||||
allowed = []uint16{53, 80, 123, 443, 1194, 65142}
|
||||
}
|
||||
|
||||
if helpers.Uint16IsOneOf(*w.EndpointPort, allowed) {
|
||||
err = validate.IsOneOf(*w.EndpointPort, allowed...)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
return fmt.Errorf("%w: %d for VPN service provider %s; %s",
|
||||
ErrWireguardEndpointPortNotAllowed, w.EndpointPort, vpnProvider,
|
||||
helpers.PortChoicesOrString(allowed))
|
||||
return fmt.Errorf("%w: for VPN service provider %s: %w",
|
||||
ErrWireguardEndpointPortNotAllowed, vpnProvider, err)
|
||||
default: // Providers not supporting Wireguard
|
||||
}
|
||||
|
||||
@@ -110,26 +111,26 @@ func (w WireguardSelection) validate(vpnProvider string) (err error) {
|
||||
func (w *WireguardSelection) copy() (copied WireguardSelection) {
|
||||
return WireguardSelection{
|
||||
EndpointIP: w.EndpointIP,
|
||||
EndpointPort: helpers.CopyPointer(w.EndpointPort),
|
||||
EndpointPort: gosettings.CopyPointer(w.EndpointPort),
|
||||
PublicKey: w.PublicKey,
|
||||
}
|
||||
}
|
||||
|
||||
func (w *WireguardSelection) mergeWith(other WireguardSelection) {
|
||||
w.EndpointIP = helpers.MergeWithIP(w.EndpointIP, other.EndpointIP)
|
||||
w.EndpointPort = helpers.MergeWithPointer(w.EndpointPort, other.EndpointPort)
|
||||
w.PublicKey = helpers.MergeWithString(w.PublicKey, other.PublicKey)
|
||||
w.EndpointIP = gosettings.MergeWithValidator(w.EndpointIP, other.EndpointIP)
|
||||
w.EndpointPort = gosettings.MergeWithPointer(w.EndpointPort, other.EndpointPort)
|
||||
w.PublicKey = gosettings.MergeWithString(w.PublicKey, other.PublicKey)
|
||||
}
|
||||
|
||||
func (w *WireguardSelection) overrideWith(other WireguardSelection) {
|
||||
w.EndpointIP = helpers.OverrideWithIP(w.EndpointIP, other.EndpointIP)
|
||||
w.EndpointPort = helpers.OverrideWithPointer(w.EndpointPort, other.EndpointPort)
|
||||
w.PublicKey = helpers.OverrideWithString(w.PublicKey, other.PublicKey)
|
||||
w.EndpointIP = gosettings.OverrideWithValidator(w.EndpointIP, other.EndpointIP)
|
||||
w.EndpointPort = gosettings.OverrideWithPointer(w.EndpointPort, other.EndpointPort)
|
||||
w.PublicKey = gosettings.OverrideWithString(w.PublicKey, other.PublicKey)
|
||||
}
|
||||
|
||||
func (w *WireguardSelection) setDefaults() {
|
||||
w.EndpointIP = helpers.DefaultIP(w.EndpointIP, netip.IPv4Unspecified())
|
||||
w.EndpointPort = helpers.DefaultPointer(w.EndpointPort, 0)
|
||||
w.EndpointIP = gosettings.DefaultValidator(w.EndpointIP, netip.IPv4Unspecified())
|
||||
w.EndpointPort = gosettings.DefaultPointer(w.EndpointPort, 0)
|
||||
}
|
||||
|
||||
func (w WireguardSelection) String() string {
|
||||
|
||||
19
internal/configuration/sources/env/dns.go
vendored
19
internal/configuration/sources/env/dns.go
vendored
@@ -13,9 +13,9 @@ func (s *Source) readDNS() (dns settings.DNS, err error) {
|
||||
return dns, err
|
||||
}
|
||||
|
||||
dns.KeepNameserver, err = envToBoolPtr("DNS_KEEP_NAMESERVER")
|
||||
dns.KeepNameserver, err = s.env.BoolPtr("DNS_KEEP_NAMESERVER")
|
||||
if err != nil {
|
||||
return dns, fmt.Errorf("environment variable DNS_KEEP_NAMESERVER: %w", err)
|
||||
return dns, err
|
||||
}
|
||||
|
||||
dns.DoT, err = s.readDoT()
|
||||
@@ -27,19 +27,24 @@ func (s *Source) readDNS() (dns settings.DNS, err error) {
|
||||
}
|
||||
|
||||
func (s *Source) readDNSServerAddress() (address netip.Addr, err error) {
|
||||
key, value := s.getEnvWithRetro("DNS_ADDRESS", "DNS_PLAINTEXT_ADDRESS")
|
||||
if value == "" {
|
||||
const currentKey = "DNS_ADDRESS"
|
||||
key := firstKeySet(s.env, "DNS_PLAINTEXT_ADDRESS", currentKey)
|
||||
switch key {
|
||||
case "":
|
||||
return address, nil
|
||||
case currentKey:
|
||||
default: // Retro-compatibility
|
||||
s.handleDeprecatedKey(key, currentKey)
|
||||
}
|
||||
|
||||
address, err = netip.ParseAddr(value)
|
||||
address, err = s.env.NetipAddr(key)
|
||||
if err != nil {
|
||||
return address, fmt.Errorf("environment variable %s: %w", key, err)
|
||||
return address, err
|
||||
}
|
||||
|
||||
// TODO remove in v4
|
||||
if address.Unmap().Compare(netip.AddrFrom4([4]byte{127, 0, 0, 1})) != 0 {
|
||||
s.warner.Warn(key + " is set to " + value +
|
||||
s.warner.Warn(key + " is set to " + address.String() +
|
||||
" 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 serves." +
|
||||
" If the DoT server fails to start, the IPv4 address of the first plaintext DNS server" +
|
||||
|
||||
@@ -6,58 +6,44 @@ import (
|
||||
"net/netip"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/govalid/binary"
|
||||
"github.com/qdm12/gosettings/sources/env"
|
||||
)
|
||||
|
||||
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 = s.readBlockSurveillance()
|
||||
blacklist.BlockMalicious, err = s.env.BoolPtr("BLOCK_MALICIOUS")
|
||||
if err != nil {
|
||||
return blacklist, err
|
||||
}
|
||||
|
||||
blacklist.BlockAds, err = envToBoolPtr("BLOCK_ADS")
|
||||
blacklist.BlockSurveillance, err = s.env.BoolPtr("BLOCK_SURVEILLANCE",
|
||||
env.RetroKeys("BLOCK_NSA"))
|
||||
if err != nil {
|
||||
return blacklist, fmt.Errorf("environment variable BLOCK_ADS: %w", err)
|
||||
return blacklist, err
|
||||
}
|
||||
|
||||
blacklist.BlockAds, err = s.env.BoolPtr("BLOCK_ADS")
|
||||
if err != nil {
|
||||
return blacklist, err
|
||||
}
|
||||
|
||||
blacklist.AddBlockedIPs, blacklist.AddBlockedIPPrefixes,
|
||||
err = readDoTPrivateAddresses() // TODO v4 split in 2
|
||||
err = s.readDoTPrivateAddresses() // TODO v4 split in 2
|
||||
if err != nil {
|
||||
return blacklist, err
|
||||
}
|
||||
|
||||
blacklist.AllowedHosts = envToCSV("UNBLOCK") // TODO v4 change name
|
||||
blacklist.AllowedHosts = s.env.CSV("UNBLOCK") // TODO v4 change name
|
||||
|
||||
return blacklist, 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 = new(bool)
|
||||
*blocked, err = binary.Validate(value)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("environment variable %s: %w", key, err)
|
||||
}
|
||||
|
||||
return blocked, nil
|
||||
}
|
||||
|
||||
var (
|
||||
ErrPrivateAddressNotValid = errors.New("private address is not a valid IP or CIDR range")
|
||||
)
|
||||
|
||||
func readDoTPrivateAddresses() (ips []netip.Addr,
|
||||
func (s *Source) readDoTPrivateAddresses() (ips []netip.Addr,
|
||||
ipPrefixes []netip.Prefix, err error) {
|
||||
privateAddresses := envToCSV("DOT_PRIVATE_ADDRESS")
|
||||
privateAddresses := s.env.CSV("DOT_PRIVATE_ADDRESS")
|
||||
if len(privateAddresses) == 0 {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
12
internal/configuration/sources/env/dot.go
vendored
12
internal/configuration/sources/env/dot.go
vendored
@@ -1,23 +1,21 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
)
|
||||
|
||||
func (s *Source) readDoT() (dot settings.DoT, err error) {
|
||||
dot.Enabled, err = envToBoolPtr("DOT")
|
||||
dot.Enabled, err = s.env.BoolPtr("DOT")
|
||||
if err != nil {
|
||||
return dot, fmt.Errorf("environment variable DOT: %w", err)
|
||||
return dot, err
|
||||
}
|
||||
|
||||
dot.UpdatePeriod, err = envToDurationPtr("DNS_UPDATE_PERIOD")
|
||||
dot.UpdatePeriod, err = s.env.DurationPtr("DNS_UPDATE_PERIOD")
|
||||
if err != nil {
|
||||
return dot, fmt.Errorf("environment variable DNS_UPDATE_PERIOD: %w", err)
|
||||
return dot, err
|
||||
}
|
||||
|
||||
dot.Unbound, err = readUnbound()
|
||||
dot.Unbound, err = s.readUnbound()
|
||||
if err != nil {
|
||||
return dot, err
|
||||
}
|
||||
|
||||
68
internal/configuration/sources/env/firewall.go
vendored
68
internal/configuration/sources/env/firewall.go
vendored
@@ -1,80 +1,36 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"strconv"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gosettings/sources/env"
|
||||
)
|
||||
|
||||
func (s *Source) readFirewall() (firewall settings.Firewall, err error) {
|
||||
vpnInputPortStrings := envToCSV("FIREWALL_VPN_INPUT_PORTS")
|
||||
firewall.VPNInputPorts, err = stringsToPorts(vpnInputPortStrings)
|
||||
firewall.VPNInputPorts, err = s.env.CSVUint16("FIREWALL_VPN_INPUT_PORTS")
|
||||
if err != nil {
|
||||
return firewall, fmt.Errorf("environment variable FIREWALL_VPN_INPUT_PORTS: %w", err)
|
||||
return firewall, err
|
||||
}
|
||||
|
||||
inputPortStrings := envToCSV("FIREWALL_INPUT_PORTS")
|
||||
firewall.InputPorts, err = stringsToPorts(inputPortStrings)
|
||||
firewall.InputPorts, err = s.env.CSVUint16("FIREWALL_INPUT_PORTS")
|
||||
if err != nil {
|
||||
return firewall, fmt.Errorf("environment variable FIREWALL_INPUT_PORTS: %w", err)
|
||||
return firewall, err
|
||||
}
|
||||
|
||||
outboundSubnetsKey, _ := s.getEnvWithRetro("FIREWALL_OUTBOUND_SUBNETS", "EXTRA_SUBNETS")
|
||||
outboundSubnetStrings := envToCSV(outboundSubnetsKey)
|
||||
firewall.OutboundSubnets, err = stringsToNetipPrefixes(outboundSubnetStrings)
|
||||
firewall.OutboundSubnets, err = s.env.CSVNetipPrefixes("FIREWALL_OUTBOUND_SUBNETS",
|
||||
env.RetroKeys("EXTRA_SUBNETS"))
|
||||
if err != nil {
|
||||
return firewall, fmt.Errorf("environment variable %s: %w", outboundSubnetsKey, err)
|
||||
return firewall, err
|
||||
}
|
||||
|
||||
firewall.Enabled, err = envToBoolPtr("FIREWALL")
|
||||
firewall.Enabled, err = s.env.BoolPtr("FIREWALL")
|
||||
if err != nil {
|
||||
return firewall, fmt.Errorf("environment variable FIREWALL: %w", err)
|
||||
return firewall, err
|
||||
}
|
||||
|
||||
firewall.Debug, err = envToBoolPtr("FIREWALL_DEBUG")
|
||||
firewall.Debug, err = s.env.BoolPtr("FIREWALL_DEBUG")
|
||||
if err != nil {
|
||||
return firewall, fmt.Errorf("environment variable FIREWALL_DEBUG: %w", err)
|
||||
return firewall, err
|
||||
}
|
||||
|
||||
return firewall, nil
|
||||
}
|
||||
|
||||
var (
|
||||
ErrPortParsing = errors.New("cannot parse port")
|
||||
ErrPortValue = errors.New("port value is not valid")
|
||||
)
|
||||
|
||||
func stringsToPorts(ss []string) (ports []uint16, err error) {
|
||||
if len(ss) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
ports = make([]uint16, len(ss))
|
||||
for i, s := range ss {
|
||||
port, err := strconv.Atoi(s)
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
ports[i] = uint16(port)
|
||||
}
|
||||
return ports, nil
|
||||
}
|
||||
|
||||
func stringsToNetipPrefixes(ss []string) (ipPrefixes []netip.Prefix, err error) {
|
||||
if len(ss) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
ipPrefixes = make([]netip.Prefix, len(ss))
|
||||
for i, s := range ss {
|
||||
ipPrefixes[i], err = netip.ParsePrefix(s)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing IP network %q: %w", s, err)
|
||||
}
|
||||
}
|
||||
return ipPrefixes, nil
|
||||
}
|
||||
|
||||
36
internal/configuration/sources/env/health.go
vendored
36
internal/configuration/sources/env/health.go
vendored
@@ -1,51 +1,35 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gosettings/sources/env"
|
||||
)
|
||||
|
||||
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.ServerAddress = s.env.String("HEALTH_SERVER_ADDRESS")
|
||||
health.TargetAddress = s.env.String("HEALTH_TARGET_ADDRESS",
|
||||
env.RetroKeys("HEALTH_ADDRESS_TO_PING"))
|
||||
|
||||
successWaitPtr, err := envToDurationPtr("HEALTH_SUCCESS_WAIT_DURATION")
|
||||
successWaitPtr, err := s.env.DurationPtr("HEALTH_SUCCESS_WAIT_DURATION")
|
||||
if err != nil {
|
||||
return health, fmt.Errorf("environment variable HEALTH_SUCCESS_WAIT_DURATION: %w", err)
|
||||
return health, err
|
||||
} else if successWaitPtr != nil {
|
||||
health.SuccessWait = *successWaitPtr
|
||||
}
|
||||
|
||||
health.VPN.Initial, err = s.readDurationWithRetro(
|
||||
health.VPN.Initial, err = s.env.DurationPtr(
|
||||
"HEALTH_VPN_DURATION_INITIAL",
|
||||
"HEALTH_OPENVPN_DURATION_INITIAL")
|
||||
env.RetroKeys("HEALTH_OPENVPN_DURATION_INITIAL"))
|
||||
if err != nil {
|
||||
return health, err
|
||||
}
|
||||
|
||||
health.VPN.Addition, err = s.readDurationWithRetro(
|
||||
health.VPN.Addition, err = s.env.DurationPtr(
|
||||
"HEALTH_VPN_DURATION_ADDITION",
|
||||
"HEALTH_OPENVPN_DURATION_ADDITION")
|
||||
env.RetroKeys("HEALTH_OPENVPN_DURATION_ADDITION"))
|
||||
if err != nil {
|
||||
return health, err
|
||||
}
|
||||
|
||||
return health, nil
|
||||
}
|
||||
|
||||
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(value)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("environment variable %s: %w", envKey, err)
|
||||
}
|
||||
|
||||
return d, nil
|
||||
}
|
||||
|
||||
135
internal/configuration/sources/env/helpers.go
vendored
135
internal/configuration/sources/env/helpers.go
vendored
@@ -3,127 +3,10 @@ package env
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/qdm12/govalid/binary"
|
||||
"github.com/qdm12/govalid/integer"
|
||||
"github.com/qdm12/gosettings/sources/env"
|
||||
)
|
||||
|
||||
// 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 := getCleanedEnv(envKey)
|
||||
if csv == "" {
|
||||
return nil
|
||||
}
|
||||
return lowerAndSplit(csv)
|
||||
}
|
||||
|
||||
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 := getCleanedEnv(envKey)
|
||||
if s == "" {
|
||||
return nil
|
||||
}
|
||||
return &s
|
||||
}
|
||||
|
||||
func envToBoolPtr(envKey string) (boolPtr *bool, err error) {
|
||||
s := getCleanedEnv(envKey)
|
||||
if s == "" {
|
||||
return nil, nil //nolint:nilnil
|
||||
}
|
||||
value, err := binary.Validate(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &value, nil
|
||||
}
|
||||
|
||||
func envToIntPtr(envKey string) (intPtr *int, err error) {
|
||||
s := getCleanedEnv(envKey)
|
||||
if s == "" {
|
||||
return nil, nil //nolint:nilnil
|
||||
}
|
||||
value, err := strconv.Atoi(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &value, nil
|
||||
}
|
||||
|
||||
func envToUint8Ptr(envKey string) (uint8Ptr *uint8, err error) {
|
||||
s := getCleanedEnv(envKey)
|
||||
if s == "" {
|
||||
return nil, nil //nolint:nilnil
|
||||
}
|
||||
|
||||
const min, max = 0, 255
|
||||
value, err := integer.Validate(s, integer.OptionRange(min, max))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
uint8Ptr = new(uint8)
|
||||
*uint8Ptr = uint8(value)
|
||||
return uint8Ptr, nil
|
||||
}
|
||||
|
||||
func envToUint16Ptr(envKey string) (uint16Ptr *uint16, err error) {
|
||||
s := getCleanedEnv(envKey)
|
||||
if s == "" {
|
||||
return nil, nil //nolint:nilnil
|
||||
}
|
||||
|
||||
const min, max = 0, 65535
|
||||
value, err := integer.Validate(s, integer.OptionRange(min, max))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
uint16Ptr = new(uint16)
|
||||
*uint16Ptr = uint16(value)
|
||||
return uint16Ptr, nil
|
||||
}
|
||||
|
||||
func envToDurationPtr(envKey string) (durationPtr *time.Duration, err error) {
|
||||
s := getCleanedEnv(envKey)
|
||||
if s == "" {
|
||||
return nil, nil //nolint:nilnil
|
||||
}
|
||||
|
||||
durationPtr = new(time.Duration)
|
||||
*durationPtr, err = time.ParseDuration(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return durationPtr, nil
|
||||
}
|
||||
|
||||
func lowerAndSplit(csv string) (values []string) {
|
||||
csv = strings.ToLower(csv)
|
||||
return strings.Split(csv, ",")
|
||||
}
|
||||
|
||||
func unsetEnvKeys(envKeys []string, err error) (newErr error) {
|
||||
newErr = err
|
||||
for _, envKey := range envKeys {
|
||||
@@ -135,6 +18,16 @@ func unsetEnvKeys(envKeys []string, err error) (newErr error) {
|
||||
return newErr
|
||||
}
|
||||
|
||||
func stringPtr(s string) *string { return &s }
|
||||
func uint32Ptr(n uint32) *uint32 { return &n }
|
||||
func boolPtr(b bool) *bool { return &b }
|
||||
func ptrTo[T any](value T) *T {
|
||||
return &value
|
||||
}
|
||||
|
||||
func firstKeySet(e env.Env, keys ...string) (firstKeySet string) {
|
||||
for _, key := range keys {
|
||||
value := e.Get(key)
|
||||
if value != nil {
|
||||
return key
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// setTestEnv is used to set environment variables in
|
||||
// parallel tests.
|
||||
func setTestEnv(t *testing.T, key, value string) {
|
||||
t.Helper()
|
||||
existing := os.Getenv(key)
|
||||
err := os.Setenv(key, value) //nolint:tenv
|
||||
t.Cleanup(func() {
|
||||
err = os.Setenv(key, existing)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
89
internal/configuration/sources/env/httproxy.go
vendored
89
internal/configuration/sources/env/httproxy.go
vendored
@@ -4,22 +4,32 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gosettings/sources/env"
|
||||
"github.com/qdm12/govalid/binary"
|
||||
)
|
||||
|
||||
func (s *Source) readHTTPProxy() (httpProxy settings.HTTPProxy, err error) {
|
||||
httpProxy.User = s.readHTTProxyUser()
|
||||
httpProxy.Password = s.readHTTProxyPassword()
|
||||
httpProxy.ListeningAddress = s.readHTTProxyListeningAddress()
|
||||
httpProxy.User = s.env.Get("HTTPPROXY_USER",
|
||||
env.RetroKeys("PROXY_USER", "TINYPROXY_USER"),
|
||||
env.ForceLowercase(false))
|
||||
|
||||
httpProxy.Enabled, err = s.readHTTProxyEnabled()
|
||||
httpProxy.Password = s.env.Get("HTTPPROXY_PASSWORD",
|
||||
env.RetroKeys("PROXY_PASSWORD", "TINYPROXY_PASSWORD"),
|
||||
env.ForceLowercase(false))
|
||||
|
||||
httpProxy.ListeningAddress, err = s.readHTTProxyListeningAddress()
|
||||
if err != nil {
|
||||
return httpProxy, err
|
||||
}
|
||||
|
||||
httpProxy.Stealth, err = envToBoolPtr("HTTPPROXY_STEALTH")
|
||||
httpProxy.Enabled, err = s.env.BoolPtr("HTTPPROXY", env.RetroKeys("PROXY", "TINYPROXY"))
|
||||
if err != nil {
|
||||
return httpProxy, fmt.Errorf("environment variable HTTPPROXY_STEALTH: %w", err)
|
||||
return httpProxy, err
|
||||
}
|
||||
|
||||
httpProxy.Stealth, err = s.env.BoolPtr("HTTPPROXY_STEALTH")
|
||||
if err != nil {
|
||||
return httpProxy, err
|
||||
}
|
||||
|
||||
httpProxy.Log, err = s.readHTTProxyLog()
|
||||
@@ -30,59 +40,42 @@ func (s *Source) readHTTPProxy() (httpProxy settings.HTTPProxy, err error) {
|
||||
return httpProxy, nil
|
||||
}
|
||||
|
||||
func (s *Source) readHTTProxyUser() (user *string) {
|
||||
_, value := s.getEnvWithRetro("HTTPPROXY_USER", "PROXY_USER", "TINYPROXY_USER")
|
||||
if value != "" {
|
||||
return &value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Source) readHTTProxyPassword() (user *string) {
|
||||
_, value := s.getEnvWithRetro("HTTPPROXY_PASSWORD", "PROXY_PASSWORD", "TINYPROXY_PASSWORD")
|
||||
if value != "" {
|
||||
return &value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
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 ":" + value
|
||||
}
|
||||
|
||||
func (s *Source) readHTTProxyEnabled() (enabled *bool, err error) {
|
||||
key, value := s.getEnvWithRetro("HTTPPROXY", "PROXY", "TINYPROXY")
|
||||
if value == "" {
|
||||
return nil, nil //nolint:nilnil
|
||||
func (s *Source) readHTTProxyListeningAddress() (listeningAddress string, err error) {
|
||||
const currentKey = "HTTPPROXY_LISTENING_ADDRESS"
|
||||
key := firstKeySet(s.env, "HTTPPROXY_PORT", "TINYPROXY_PORT", "PROXY_PORT",
|
||||
currentKey)
|
||||
switch key {
|
||||
case "":
|
||||
return "", nil
|
||||
case currentKey:
|
||||
return s.env.String(key), nil
|
||||
}
|
||||
|
||||
enabled = new(bool)
|
||||
*enabled, err = binary.Validate(value)
|
||||
// Retro-compatible keys using a port only
|
||||
s.handleDeprecatedKey(key, currentKey)
|
||||
port, err := s.env.Uint16Ptr(key)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("environment variable %s: %w", key, err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
return enabled, nil
|
||||
return fmt.Sprintf(":%d", *port), nil
|
||||
}
|
||||
|
||||
func (s *Source) readHTTProxyLog() (enabled *bool, err error) {
|
||||
key, value := s.getEnvWithRetro("HTTPPROXY_LOG", "PROXY_LOG_LEVEL", "TINYPROXY_LOG")
|
||||
if value == "" {
|
||||
const currentKey = "HTTPPROXY_LOG"
|
||||
key := firstKeySet(s.env, "PROXY_LOG", "TINYPROXY_LOG", "HTTPPROXY_LOG")
|
||||
switch key {
|
||||
case "":
|
||||
return nil, nil //nolint:nilnil
|
||||
case currentKey:
|
||||
return s.env.BoolPtr(key)
|
||||
}
|
||||
|
||||
var binaryOptions []binary.Option
|
||||
if key != "HTTPROXY_LOG" {
|
||||
retroOption := binary.OptionEnabled("on", "info", "connect", "notice")
|
||||
binaryOptions = append(binaryOptions, retroOption)
|
||||
}
|
||||
// Retro-compatible keys using different boolean verbs
|
||||
s.handleDeprecatedKey(key, currentKey)
|
||||
value := s.env.String(key)
|
||||
retroOption := binary.OptionEnabled("on", "info", "connect", "notice")
|
||||
|
||||
enabled = new(bool)
|
||||
*enabled, err = binary.Validate(value, binaryOptions...)
|
||||
enabled, err = binary.Validate(value, retroOption)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("environment variable %s: %w", key, err)
|
||||
}
|
||||
|
||||
12
internal/configuration/sources/env/log.go
vendored
12
internal/configuration/sources/env/log.go
vendored
@@ -9,8 +9,8 @@ import (
|
||||
"github.com/qdm12/log"
|
||||
)
|
||||
|
||||
func readLog() (log settings.Log, err error) {
|
||||
log.Level, err = readLogLevel()
|
||||
func (s *Source) readLog() (log settings.Log, err error) {
|
||||
log.Level, err = s.readLogLevel()
|
||||
if err != nil {
|
||||
return log, err
|
||||
}
|
||||
@@ -18,14 +18,14 @@ func readLog() (log settings.Log, err error) {
|
||||
return log, nil
|
||||
}
|
||||
|
||||
func readLogLevel() (level *log.Level, err error) {
|
||||
s := getCleanedEnv("LOG_LEVEL")
|
||||
if s == "" {
|
||||
func (s *Source) readLogLevel() (level *log.Level, err error) {
|
||||
value := s.env.String("LOG_LEVEL")
|
||||
if value == "" {
|
||||
return nil, nil //nolint:nilnil
|
||||
}
|
||||
|
||||
level = new(log.Level)
|
||||
*level, err = parseLogLevel(s)
|
||||
*level, err = parseLogLevel(value)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("environment variable LOG_LEVEL: %w", err)
|
||||
}
|
||||
|
||||
118
internal/configuration/sources/env/openvpn.go
vendored
118
internal/configuration/sources/env/openvpn.go
vendored
@@ -1,11 +1,10 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/govalid/binary"
|
||||
"github.com/qdm12/gosettings/sources/env"
|
||||
)
|
||||
|
||||
func (s *Source) readOpenVPN() (
|
||||
@@ -15,113 +14,64 @@ func (s *Source) readOpenVPN() (
|
||||
"OPENVPN_KEY_PASSPHRASE", "OPENVPN_ENCRYPTED_KEY"}, err)
|
||||
}()
|
||||
|
||||
openVPN.Version = getCleanedEnv("OPENVPN_VERSION")
|
||||
openVPN.User = s.readOpenVPNUser()
|
||||
openVPN.Password = s.readOpenVPNPassword()
|
||||
confFile := getCleanedEnv("OPENVPN_CUSTOM_CONFIG")
|
||||
if confFile != "" {
|
||||
openVPN.ConfFile = &confFile
|
||||
}
|
||||
|
||||
ciphersKey, _ := s.getEnvWithRetro("OPENVPN_CIPHERS", "OPENVPN_CIPHER")
|
||||
openVPN.Ciphers = envToCSV(ciphersKey)
|
||||
|
||||
auth := getCleanedEnv("OPENVPN_AUTH")
|
||||
if auth != "" {
|
||||
openVPN.Auth = &auth
|
||||
}
|
||||
|
||||
openVPN.Cert = envToStringPtr("OPENVPN_CERT")
|
||||
openVPN.Key = envToStringPtr("OPENVPN_KEY")
|
||||
openVPN.EncryptedKey = envToStringPtr("OPENVPN_ENCRYPTED_KEY")
|
||||
|
||||
openVPN.KeyPassphrase = s.readOpenVPNKeyPassphrase()
|
||||
openVPN.Version = s.env.String("OPENVPN_VERSION")
|
||||
openVPN.User = s.env.Get("OPENVPN_USER",
|
||||
env.RetroKeys("USER"), env.ForceLowercase(false))
|
||||
openVPN.Password = s.env.Get("OPENVPN_PASSWORD",
|
||||
env.RetroKeys("PASSWORD"), env.ForceLowercase(false))
|
||||
openVPN.ConfFile = s.env.Get("OPENVPN_CUSTOM_CONFIG", env.ForceLowercase(false))
|
||||
openVPN.Ciphers = s.env.CSV("OPENVPN_CIPHERS", env.RetroKeys("OPENVPN_CIPHER"))
|
||||
openVPN.Auth = s.env.Get("OPENVPN_AUTH")
|
||||
openVPN.Cert = s.env.Get("OPENVPN_CERT", env.ForceLowercase(false))
|
||||
openVPN.Key = s.env.Get("OPENVPN_KEY", env.ForceLowercase(false))
|
||||
openVPN.EncryptedKey = s.env.Get("OPENVPN_ENCRYPTED_KEY", env.ForceLowercase(false))
|
||||
openVPN.KeyPassphrase = s.env.Get("OPENVPN_KEY_PASSPHRASE", env.ForceLowercase(false))
|
||||
|
||||
openVPN.PIAEncPreset = s.readPIAEncryptionPreset()
|
||||
|
||||
openVPN.MSSFix, err = envToUint16Ptr("OPENVPN_MSSFIX")
|
||||
openVPN.MSSFix, err = s.env.Uint16Ptr("OPENVPN_MSSFIX")
|
||||
if err != nil {
|
||||
return openVPN, fmt.Errorf("environment variable OPENVPN_MSSFIX: %w", err)
|
||||
return openVPN, err
|
||||
}
|
||||
|
||||
_, openVPN.Interface = s.getEnvWithRetro("VPN_INTERFACE", "OPENVPN_INTERFACE")
|
||||
openVPN.Interface = s.env.String("VPN_INTERFACE",
|
||||
env.RetroKeys("OPENVPN_INTERFACE"), env.ForceLowercase(false))
|
||||
|
||||
openVPN.ProcessUser, err = s.readOpenVPNProcessUser()
|
||||
if err != nil {
|
||||
return openVPN, err
|
||||
}
|
||||
|
||||
openVPN.Verbosity, err = envToIntPtr("OPENVPN_VERBOSITY")
|
||||
openVPN.Verbosity, err = s.env.IntPtr("OPENVPN_VERBOSITY")
|
||||
if err != nil {
|
||||
return openVPN, fmt.Errorf("environment variable OPENVPN_VERBOSITY: %w", err)
|
||||
return openVPN, err
|
||||
}
|
||||
|
||||
flagsStr := getCleanedEnv("OPENVPN_FLAGS")
|
||||
if flagsStr != "" {
|
||||
openVPN.Flags = strings.Fields(flagsStr)
|
||||
flagsPtr := s.env.Get("OPENVPN_FLAGS", env.ForceLowercase(false))
|
||||
if flagsPtr != nil {
|
||||
openVPN.Flags = strings.Fields(*flagsPtr)
|
||||
}
|
||||
|
||||
return openVPN, nil
|
||||
}
|
||||
|
||||
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
|
||||
*user = strings.ReplaceAll(*user, " ", "")
|
||||
return user
|
||||
}
|
||||
|
||||
func (s *Source) readOpenVPNPassword() (password *string) {
|
||||
password = new(string)
|
||||
_, *password = s.getEnvWithRetro("OPENVPN_PASSWORD", "PASSWORD")
|
||||
if *password == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
return password
|
||||
}
|
||||
|
||||
func (s *Source) readOpenVPNKeyPassphrase() (passphrase *string) {
|
||||
passphrase = new(string)
|
||||
*passphrase = getCleanedEnv("OPENVPN_KEY_PASSPHRASE")
|
||||
if *passphrase == "" {
|
||||
return nil
|
||||
}
|
||||
return passphrase
|
||||
}
|
||||
|
||||
func (s *Source) readPIAEncryptionPreset() (presetPtr *string) {
|
||||
_, preset := s.getEnvWithRetro(
|
||||
return s.env.Get(
|
||||
"PRIVATE_INTERNET_ACCESS_OPENVPN_ENCRYPTION_PRESET",
|
||||
"PIA_ENCRYPTION", "ENCRYPTION")
|
||||
if preset != "" {
|
||||
return &preset
|
||||
}
|
||||
return nil
|
||||
env.RetroKeys("ENCRYPTION", "PIA_ENCRYPTION"))
|
||||
}
|
||||
|
||||
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
|
||||
value, err := s.env.BoolPtr("OPENVPN_ROOT") // Retro-compatibility
|
||||
if err != nil {
|
||||
return "", err
|
||||
} else if value != nil {
|
||||
if *value {
|
||||
return "root", nil
|
||||
}
|
||||
const defaultNonRootUser = "nonrootuser"
|
||||
return defaultNonRootUser, nil
|
||||
}
|
||||
|
||||
// Retro-compatibility
|
||||
if value == "" {
|
||||
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
|
||||
return s.env.String("OPENVPN_PROCESS_USER"), nil
|
||||
}
|
||||
|
||||
@@ -7,22 +7,20 @@ import (
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/govalid/port"
|
||||
"github.com/qdm12/gosettings/sources/env"
|
||||
)
|
||||
|
||||
func (s *Source) readOpenVPNSelection() (
|
||||
selection settings.OpenVPNSelection, err error) {
|
||||
confFile := getCleanedEnv("OPENVPN_CUSTOM_CONFIG")
|
||||
if confFile != "" {
|
||||
selection.ConfFile = &confFile
|
||||
}
|
||||
selection.ConfFile = s.env.Get("OPENVPN_CUSTOM_CONFIG", env.ForceLowercase(false))
|
||||
|
||||
selection.TCP, err = s.readOpenVPNProtocol()
|
||||
if err != nil {
|
||||
return selection, err
|
||||
}
|
||||
|
||||
selection.CustomPort, err = s.readOpenVPNCustomPort()
|
||||
selection.CustomPort, err = s.env.Uint16Ptr("VPN_ENDPOINT_PORT",
|
||||
env.RetroKeys("PORT", "OPENVPN_PORT"))
|
||||
if err != nil {
|
||||
return selection, err
|
||||
}
|
||||
@@ -35,32 +33,24 @@ func (s *Source) readOpenVPNSelection() (
|
||||
var ErrOpenVPNProtocolNotValid = errors.New("OpenVPN protocol is not valid")
|
||||
|
||||
func (s *Source) readOpenVPNProtocol() (tcp *bool, err error) {
|
||||
envKey, protocol := s.getEnvWithRetro("OPENVPN_PROTOCOL", "PROTOCOL")
|
||||
|
||||
switch strings.ToLower(protocol) {
|
||||
const currentKey = "OPENVPN_PROTOCOL"
|
||||
envKey := firstKeySet(s.env, "PROTOCOL", currentKey)
|
||||
switch envKey {
|
||||
case "":
|
||||
return nil, nil //nolint:nilnil
|
||||
case currentKey:
|
||||
default: // Retro compatibility
|
||||
s.handleDeprecatedKey(envKey, currentKey)
|
||||
}
|
||||
|
||||
protocol := s.env.String(envKey)
|
||||
switch strings.ToLower(protocol) {
|
||||
case constants.UDP:
|
||||
return boolPtr(false), nil
|
||||
return ptrTo(false), nil
|
||||
case constants.TCP:
|
||||
return boolPtr(true), nil
|
||||
return ptrTo(true), nil
|
||||
default:
|
||||
return nil, fmt.Errorf("environment variable %s: %w: %s",
|
||||
envKey, ErrOpenVPNProtocolNotValid, protocol)
|
||||
}
|
||||
}
|
||||
|
||||
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(value)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("environment variable %s: %w", key, err)
|
||||
}
|
||||
|
||||
return customPort, nil
|
||||
}
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gosettings/sources/env"
|
||||
)
|
||||
|
||||
func (s *Source) readPortForward() (
|
||||
portForwarding settings.PortForwarding, err error) {
|
||||
key, _ := s.getEnvWithRetro(
|
||||
"VPN_PORT_FORWARDING",
|
||||
"PRIVATE_INTERNET_ACCESS_VPN_PORT_FORWARDING",
|
||||
"PORT_FORWARDING")
|
||||
portForwarding.Enabled, err = envToBoolPtr(key)
|
||||
portForwarding.Enabled, err = s.env.BoolPtr("VPN_PORT_FORWARDING",
|
||||
env.RetroKeys(
|
||||
"PORT_FORWARDING",
|
||||
"PRIVATE_INTERNET_ACCESS_VPN_PORT_FORWARDING",
|
||||
))
|
||||
if err != nil {
|
||||
return portForwarding, fmt.Errorf("environment variable %s: %w", key, err)
|
||||
return portForwarding, err
|
||||
}
|
||||
|
||||
_, value := s.getEnvWithRetro(
|
||||
"VPN_PORT_FORWARDING_STATUS_FILE",
|
||||
"PRIVATE_INTERNET_ACCESS_VPN_PORT_FORWARDING_STATUS_FILE",
|
||||
"PORT_FORWARDING_STATUS_FILE")
|
||||
if value != "" {
|
||||
portForwarding.Filepath = stringPtr(value)
|
||||
}
|
||||
portForwarding.Provider = s.env.Get("VPN_PORT_FORWARDING_PROVIDER")
|
||||
|
||||
portForwarding.Filepath = s.env.Get("VPN_PORT_FORWARDING_STATUS_FILE",
|
||||
env.ForceLowercase(false),
|
||||
env.RetroKeys(
|
||||
"PORT_FORWARDING_STATUS_FILE",
|
||||
"PRIVATE_INTERNET_ACCESS_VPN_PORT_FORWARDING_STATUS_FILE",
|
||||
))
|
||||
|
||||
return portForwarding, nil
|
||||
}
|
||||
|
||||
18
internal/configuration/sources/env/pprof.go
vendored
18
internal/configuration/sources/env/pprof.go
vendored
@@ -1,28 +1,26 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/pprof"
|
||||
)
|
||||
|
||||
func readPprof() (settings pprof.Settings, err error) {
|
||||
settings.Enabled, err = envToBoolPtr("PPROF_ENABLED")
|
||||
func (s *Source) readPprof() (settings pprof.Settings, err error) {
|
||||
settings.Enabled, err = s.env.BoolPtr("PPROF_ENABLED")
|
||||
if err != nil {
|
||||
return settings, fmt.Errorf("environment variable PPROF_ENABLED: %w", err)
|
||||
return settings, err
|
||||
}
|
||||
|
||||
settings.BlockProfileRate, err = envToIntPtr("PPROF_BLOCK_PROFILE_RATE")
|
||||
settings.BlockProfileRate, err = s.env.IntPtr("PPROF_BLOCK_PROFILE_RATE")
|
||||
if err != nil {
|
||||
return settings, fmt.Errorf("environment variable PPROF_BLOCK_PROFILE_RATE: %w", err)
|
||||
return settings, err
|
||||
}
|
||||
|
||||
settings.MutexProfileRate, err = envToIntPtr("PPROF_MUTEX_PROFILE_RATE")
|
||||
settings.MutexProfileRate, err = s.env.IntPtr("PPROF_MUTEX_PROFILE_RATE")
|
||||
if err != nil {
|
||||
return settings, fmt.Errorf("environment variable PPROF_MUTEX_PROFILE_RATE: %w", err)
|
||||
return settings, err
|
||||
}
|
||||
|
||||
settings.HTTPServer.Address = getCleanedEnv("PPROF_HTTP_SERVER_ADDRESS")
|
||||
settings.HTTPServer.Address = s.env.String("PPROF_HTTP_SERVER_ADDRESS")
|
||||
|
||||
return settings, nil
|
||||
}
|
||||
|
||||
14
internal/configuration/sources/env/provider.go
vendored
14
internal/configuration/sources/env/provider.go
vendored
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||
"github.com/qdm12/gluetun/internal/constants/vpn"
|
||||
"github.com/qdm12/gosettings/sources/env"
|
||||
)
|
||||
|
||||
func (s *Source) readProvider(vpnType string) (provider settings.Provider, err error) {
|
||||
@@ -30,19 +31,20 @@ func (s *Source) readProvider(vpnType string) (provider settings.Provider, err e
|
||||
}
|
||||
|
||||
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") != "" {
|
||||
valuePtr := s.env.Get("VPN_SERVICE_PROVIDER", env.RetroKeys("VPNSP"))
|
||||
if valuePtr == nil {
|
||||
if vpnType != vpn.Wireguard && s.env.Get("OPENVPN_CUSTOM_CONFIG") != nil {
|
||||
// retro compatibility
|
||||
return stringPtr(providers.Custom)
|
||||
return ptrTo(providers.Custom)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
value := *valuePtr
|
||||
value = strings.ToLower(value)
|
||||
if value == "pia" { // retro compatibility
|
||||
return stringPtr(providers.PrivateInternetAccess)
|
||||
return ptrTo(providers.PrivateInternetAccess)
|
||||
}
|
||||
|
||||
return stringPtr(value)
|
||||
return ptrTo(value)
|
||||
}
|
||||
|
||||
32
internal/configuration/sources/env/publicip.go
vendored
32
internal/configuration/sources/env/publicip.go
vendored
@@ -1,42 +1,18 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gosettings/sources/env"
|
||||
)
|
||||
|
||||
func (s *Source) readPublicIP() (publicIP settings.PublicIP, err error) {
|
||||
publicIP.Period, err = readPublicIPPeriod()
|
||||
publicIP.Period, err = s.env.DurationPtr("PUBLICIP_PERIOD")
|
||||
if err != nil {
|
||||
return publicIP, err
|
||||
}
|
||||
|
||||
publicIP.IPFilepath = s.readPublicIPFilepath()
|
||||
publicIP.IPFilepath = s.env.Get("PUBLICIP_FILE",
|
||||
env.ForceLowercase(false), env.RetroKeys("IP_STATUS_FILE"))
|
||||
|
||||
return publicIP, nil
|
||||
}
|
||||
|
||||
func readPublicIPPeriod() (period *time.Duration, err error) {
|
||||
s := getCleanedEnv("PUBLICIP_PERIOD")
|
||||
if s == "" {
|
||||
return nil, nil //nolint:nilnil
|
||||
}
|
||||
|
||||
period = new(time.Duration)
|
||||
*period, err = time.ParseDuration(s)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("environment variable PUBLICIP_PERIOD: %w", err)
|
||||
}
|
||||
|
||||
return period, nil
|
||||
}
|
||||
|
||||
func (s *Source) readPublicIPFilepath() (filepath *string) {
|
||||
_, value := s.getEnvWithRetro("PUBLICIP_FILE", "IP_STATUS_FILE")
|
||||
if value != "" {
|
||||
return &value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
52
internal/configuration/sources/env/reader.go
vendored
52
internal/configuration/sources/env/reader.go
vendored
@@ -1,11 +1,16 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gosettings/sources/env"
|
||||
)
|
||||
|
||||
type Source struct {
|
||||
warner Warner
|
||||
env env.Env
|
||||
warner Warner
|
||||
handleDeprecatedKey func(deprecatedKey, newKey string)
|
||||
}
|
||||
|
||||
type Warner interface {
|
||||
@@ -13,8 +18,16 @@ type Warner interface {
|
||||
}
|
||||
|
||||
func New(warner Warner) *Source {
|
||||
handleDeprecatedKey := func(deprecatedKey, newKey string) {
|
||||
warner.Warn(
|
||||
"You are using the old environment variable " + deprecatedKey +
|
||||
", please consider changing it to " + newKey)
|
||||
}
|
||||
|
||||
return &Source{
|
||||
warner: warner,
|
||||
env: *env.New(os.Environ(), handleDeprecatedKey),
|
||||
warner: warner,
|
||||
handleDeprecatedKey: handleDeprecatedKey,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +59,7 @@ func (s *Source) Read() (settings settings.Settings, err error) {
|
||||
return settings, err
|
||||
}
|
||||
|
||||
settings.Log, err = readLog()
|
||||
settings.Log, err = s.readLog()
|
||||
if err != nil {
|
||||
return settings, err
|
||||
}
|
||||
@@ -56,12 +69,12 @@ func (s *Source) Read() (settings settings.Settings, err error) {
|
||||
return settings, err
|
||||
}
|
||||
|
||||
settings.Updater, err = readUpdater()
|
||||
settings.Updater, err = s.readUpdater()
|
||||
if err != nil {
|
||||
return settings, err
|
||||
}
|
||||
|
||||
settings.Version, err = readVersion()
|
||||
settings.Version, err = s.readVersion()
|
||||
if err != nil {
|
||||
return settings, err
|
||||
}
|
||||
@@ -81,37 +94,10 @@ func (s *Source) Read() (settings settings.Settings, err error) {
|
||||
return settings, err
|
||||
}
|
||||
|
||||
settings.Pprof, err = readPprof()
|
||||
settings.Pprof, err = s.readPprof()
|
||||
if err != nil {
|
||||
return settings, err
|
||||
}
|
||||
|
||||
return settings, nil
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
39
internal/configuration/sources/env/server.go
vendored
39
internal/configuration/sources/env/server.go
vendored
@@ -1,14 +1,11 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/govalid/binary"
|
||||
)
|
||||
|
||||
func (s *Source) readControlServer() (controlServer settings.ControlServer, err error) {
|
||||
controlServer.Log, err = readControlServerLog()
|
||||
controlServer.Log, err = s.env.BoolPtr("HTTP_CONTROL_SERVER_LOG")
|
||||
if err != nil {
|
||||
return controlServer, err
|
||||
}
|
||||
@@ -18,31 +15,17 @@ func (s *Source) readControlServer() (controlServer settings.ControlServer, err
|
||||
return controlServer, nil
|
||||
}
|
||||
|
||||
func readControlServerLog() (enabled *bool, err error) {
|
||||
s := getCleanedEnv("HTTP_CONTROL_SERVER_LOG")
|
||||
if s == "" {
|
||||
return nil, nil //nolint:nilnil
|
||||
}
|
||||
|
||||
log, err := binary.Validate(s)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("environment variable HTTP_CONTROL_SERVER_LOG: %w", err)
|
||||
}
|
||||
|
||||
return &log, nil
|
||||
}
|
||||
|
||||
func (s *Source) readControlServerAddress() (address *string) {
|
||||
key, value := s.getEnvWithRetro("HTTP_CONTROL_SERVER_ADDRESS", "HTTP_CONTROL_SERVER_PORT")
|
||||
if value == "" {
|
||||
const currentKey = "HTTP_CONTROL_SERVER_ADDRESS"
|
||||
key := firstKeySet(s.env, "CONTROL_SERVER_ADDRESS", currentKey)
|
||||
if key == currentKey {
|
||||
return s.env.Get(key)
|
||||
}
|
||||
|
||||
s.handleDeprecatedKey(key, currentKey)
|
||||
value := s.env.Get("CONTROL_SERVER_ADDRESS")
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if key == "HTTP_CONTROL_SERVER_ADDRESS" {
|
||||
return &value
|
||||
}
|
||||
|
||||
address = new(string)
|
||||
*address = ":" + value
|
||||
return address
|
||||
return ptrTo(":" + *value)
|
||||
}
|
||||
|
||||
@@ -2,98 +2,69 @@ package env
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrServerNumberNotValid = errors.New("server number is not valid")
|
||||
"github.com/qdm12/gosettings/sources/env"
|
||||
)
|
||||
|
||||
func (s *Source) readServerSelection(vpnProvider, vpnType string) (
|
||||
ss settings.ServerSelection, err error) {
|
||||
ss.VPN = vpnType
|
||||
|
||||
ss.TargetIP, err = s.readOpenVPNTargetIP()
|
||||
ss.TargetIP, err = s.env.NetipAddr("VPN_ENDPOINT_IP",
|
||||
env.RetroKeys("OPENVPN_TARGET_IP"))
|
||||
if err != nil {
|
||||
return ss, err
|
||||
}
|
||||
|
||||
countriesKey, _ := s.getEnvWithRetro("SERVER_COUNTRIES", "COUNTRY")
|
||||
ss.Countries = envToCSV(countriesKey)
|
||||
ss.Countries = s.env.CSV("SERVER_COUNTRIES", env.RetroKeys("COUNTRY"))
|
||||
if vpnProvider == providers.Cyberghost && len(ss.Countries) == 0 {
|
||||
// Retro-compatibility for Cyberghost using the REGION variable
|
||||
ss.Countries = envToCSV("REGION")
|
||||
ss.Countries = s.env.CSV("REGION")
|
||||
if len(ss.Countries) > 0 {
|
||||
s.onRetroActive("REGION", "SERVER_COUNTRIES")
|
||||
s.handleDeprecatedKey("REGION", "SERVER_COUNTRIES")
|
||||
}
|
||||
}
|
||||
|
||||
regionsKey, _ := s.getEnvWithRetro("SERVER_REGIONS", "REGION")
|
||||
ss.Regions = envToCSV(regionsKey)
|
||||
|
||||
citiesKey, _ := s.getEnvWithRetro("SERVER_CITIES", "CITY")
|
||||
ss.Cities = envToCSV(citiesKey)
|
||||
|
||||
ss.ISPs = envToCSV("ISP")
|
||||
|
||||
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 {
|
||||
const base, bitSize = 10, 16
|
||||
number, err := strconv.ParseInt(numberString, base, bitSize)
|
||||
if err != nil {
|
||||
return ss, fmt.Errorf("%w: %s",
|
||||
ErrServerNumberNotValid, numberString)
|
||||
} else if number < 0 || number > 65535 {
|
||||
return ss, fmt.Errorf("%w: %d must be between 0 and 65535",
|
||||
ErrServerNumberNotValid, number)
|
||||
}
|
||||
numbers[i] = uint16(number)
|
||||
}
|
||||
ss.Numbers = numbers
|
||||
ss.Regions = s.env.CSV("SERVER_REGIONS", env.RetroKeys("REGION"))
|
||||
ss.Cities = s.env.CSV("SERVER_CITIES", env.RetroKeys("CITY"))
|
||||
ss.ISPs = s.env.CSV("ISP")
|
||||
ss.Hostnames = s.env.CSV("SERVER_HOSTNAMES", env.RetroKeys("SERVER_HOSTNAME"))
|
||||
ss.Names = s.env.CSV("SERVER_NAMES", env.RetroKeys("SERVER_NAME"))
|
||||
ss.Numbers, err = s.env.CSVUint16("SERVER_NUMBER")
|
||||
if err != nil {
|
||||
return ss, err
|
||||
}
|
||||
|
||||
// Mullvad only
|
||||
ss.OwnedOnly, err = s.readOwnedOnly()
|
||||
ss.OwnedOnly, err = s.env.BoolPtr("OWNED_ONLY", env.RetroKeys("OWNED"))
|
||||
if err != nil {
|
||||
return ss, err
|
||||
}
|
||||
|
||||
// VPNUnlimited and ProtonVPN only
|
||||
ss.FreeOnly, err = envToBoolPtr("FREE_ONLY")
|
||||
ss.FreeOnly, err = s.env.BoolPtr("FREE_ONLY")
|
||||
if err != nil {
|
||||
return ss, fmt.Errorf("environment variable FREE_ONLY: %w", err)
|
||||
return ss, err
|
||||
}
|
||||
|
||||
// VPNSecure only
|
||||
ss.PremiumOnly, err = envToBoolPtr("PREMIUM_ONLY")
|
||||
ss.PremiumOnly, err = s.env.BoolPtr("PREMIUM_ONLY")
|
||||
if err != nil {
|
||||
return ss, fmt.Errorf("environment variable PREMIUM_ONLY: %w", err)
|
||||
return ss, err
|
||||
}
|
||||
|
||||
// VPNUnlimited only
|
||||
ss.MultiHopOnly, err = envToBoolPtr("MULTIHOP_ONLY")
|
||||
ss.MultiHopOnly, err = s.env.BoolPtr("MULTIHOP_ONLY")
|
||||
if err != nil {
|
||||
return ss, fmt.Errorf("environment variable MULTIHOP_ONLY: %w", err)
|
||||
return ss, err
|
||||
}
|
||||
|
||||
// VPNUnlimited only
|
||||
ss.MultiHopOnly, err = envToBoolPtr("STREAM_ONLY")
|
||||
ss.MultiHopOnly, err = s.env.BoolPtr("STREAM_ONLY")
|
||||
if err != nil {
|
||||
return ss, fmt.Errorf("environment variable STREAM_ONLY: %w", err)
|
||||
return ss, err
|
||||
}
|
||||
|
||||
ss.OpenVPN, err = s.readOpenVPNSelection()
|
||||
@@ -112,26 +83,3 @@ func (s *Source) readServerSelection(vpnProvider, vpnType string) (
|
||||
var (
|
||||
ErrInvalidIP = errors.New("invalid IP address")
|
||||
)
|
||||
|
||||
func (s *Source) readOpenVPNTargetIP() (ip netip.Addr, err error) {
|
||||
envKey, value := s.getEnvWithRetro("VPN_ENDPOINT_IP", "OPENVPN_TARGET_IP")
|
||||
if value == "" {
|
||||
return ip, nil
|
||||
}
|
||||
|
||||
ip, err = netip.ParseAddr(value)
|
||||
if err != nil {
|
||||
return ip, fmt.Errorf("environment variable %s: %w", envKey, err)
|
||||
}
|
||||
|
||||
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,43 +2,41 @@ package env
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gosettings/sources/env"
|
||||
)
|
||||
|
||||
func (s *Source) readShadowsocks() (shadowsocks settings.Shadowsocks, err error) {
|
||||
shadowsocks.Enabled, err = envToBoolPtr("SHADOWSOCKS")
|
||||
shadowsocks.Enabled, err = s.env.BoolPtr("SHADOWSOCKS")
|
||||
if err != nil {
|
||||
return shadowsocks, fmt.Errorf("environment variable SHADOWSOCKS: %w", err)
|
||||
return shadowsocks, err
|
||||
}
|
||||
|
||||
shadowsocks.Address = s.readShadowsocksAddress()
|
||||
shadowsocks.LogAddresses, err = envToBoolPtr("SHADOWSOCKS_LOG")
|
||||
shadowsocks.Address, err = s.readShadowsocksAddress()
|
||||
if err != nil {
|
||||
return shadowsocks, fmt.Errorf("environment variable SHADOWSOCKS_LOG: %w", err)
|
||||
return shadowsocks, err
|
||||
}
|
||||
shadowsocks.CipherName = s.readShadowsocksCipher()
|
||||
shadowsocks.Password = envToStringPtr("SHADOWSOCKS_PASSWORD")
|
||||
shadowsocks.LogAddresses, err = s.env.BoolPtr("SHADOWSOCKS_LOG")
|
||||
if err != nil {
|
||||
return shadowsocks, err
|
||||
}
|
||||
shadowsocks.CipherName = s.env.String("SHADOWSOCKS_CIPHER",
|
||||
env.RetroKeys("SHADOWSOCKS_METHOD"))
|
||||
shadowsocks.Password = s.env.Get("SHADOWSOCKS_PASSWORD", env.ForceLowercase(false))
|
||||
|
||||
return shadowsocks, nil
|
||||
}
|
||||
|
||||
func (s *Source) readShadowsocksAddress() (address string) {
|
||||
key, value := s.getEnvWithRetro("SHADOWSOCKS_LISTENING_ADDRESS", "SHADOWSOCKS_PORT")
|
||||
if value == "" {
|
||||
return ""
|
||||
func (s *Source) readShadowsocksAddress() (address *string, err error) {
|
||||
const currentKey = "SHADOWSOCKS_LISTENING_ADDRESS"
|
||||
port, err := s.env.Uint16Ptr("SHADOWSOCKS_PORT") // retro-compatibility
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if port != nil {
|
||||
s.handleDeprecatedKey("SHADOWSOCKS_PORT", currentKey)
|
||||
return ptrTo(fmt.Sprintf(":%d", *port)), nil
|
||||
}
|
||||
|
||||
if key == "SHADOWSOCKS_LISTENING_ADDRESS" {
|
||||
return value
|
||||
}
|
||||
|
||||
// Retro-compatibility
|
||||
return ":" + value
|
||||
}
|
||||
|
||||
func (s *Source) readShadowsocksCipher() (cipher string) {
|
||||
_, cipher = s.getEnvWithRetro("SHADOWSOCKS_CIPHER", "SHADOWSOCKS_METHOD")
|
||||
return strings.ToLower(cipher)
|
||||
return s.env.Get(currentKey), nil
|
||||
}
|
||||
|
||||
41
internal/configuration/sources/env/system.go
vendored
41
internal/configuration/sources/env/system.go
vendored
@@ -1,55 +1,22 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrSystemPUIDNotValid = errors.New("PUID is not valid")
|
||||
ErrSystemPGIDNotValid = errors.New("PGID is not valid")
|
||||
ErrSystemTimezoneNotValid = errors.New("timezone is not valid")
|
||||
"github.com/qdm12/gosettings/sources/env"
|
||||
)
|
||||
|
||||
func (s *Source) readSystem() (system settings.System, err error) {
|
||||
system.PUID, err = s.readID("PUID", "UID")
|
||||
system.PUID, err = s.env.Uint32Ptr("PUID", env.RetroKeys("UID"))
|
||||
if err != nil {
|
||||
return system, err
|
||||
}
|
||||
|
||||
system.PGID, err = s.readID("PGID", "GID")
|
||||
system.PGID, err = s.env.Uint32Ptr("PGID", env.RetroKeys("GID"))
|
||||
if err != nil {
|
||||
return system, err
|
||||
}
|
||||
|
||||
system.Timezone = getCleanedEnv("TZ")
|
||||
system.Timezone = s.env.String("TZ")
|
||||
|
||||
return system, nil
|
||||
}
|
||||
|
||||
var ErrSystemIDNotValid = errors.New("system ID is not valid")
|
||||
|
||||
func (s *Source) readID(key, retroKey string) (
|
||||
id *uint32, err error) {
|
||||
idEnvKey, idString := s.getEnvWithRetro(key, retroKey)
|
||||
if idString == "" {
|
||||
return nil, nil //nolint:nilnil
|
||||
}
|
||||
|
||||
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",
|
||||
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 uint32Ptr(uint32(idUint64)), nil
|
||||
}
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_Reader_readID(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := map[string]struct {
|
||||
keyPrefix string
|
||||
keyValue string
|
||||
retroKeyPrefix string
|
||||
retroValue string
|
||||
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: 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`,
|
||||
},
|
||||
}
|
||||
|
||||
for name, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
suffix := t.Name()
|
||||
key := testCase.keyPrefix + suffix
|
||||
retroKey := testCase.retroKeyPrefix + suffix
|
||||
|
||||
setTestEnv(t, key, testCase.keyValue)
|
||||
setTestEnv(t, retroKey, testCase.retroValue)
|
||||
|
||||
source := &Source{}
|
||||
id, err := source.readID(key, retroKey)
|
||||
|
||||
assert.ErrorIs(t, err, testCase.errWrapped)
|
||||
if err != nil {
|
||||
assert.EqualError(t, err, testCase.errMessage)
|
||||
}
|
||||
|
||||
assert.Equal(t, testCase.id, id)
|
||||
})
|
||||
}
|
||||
}
|
||||
26
internal/configuration/sources/env/unbound.go
vendored
26
internal/configuration/sources/env/unbound.go
vendored
@@ -1,37 +1,35 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
)
|
||||
|
||||
func readUnbound() (unbound settings.Unbound, err error) {
|
||||
unbound.Providers = envToCSV("DOT_PROVIDERS")
|
||||
func (s *Source) readUnbound() (unbound settings.Unbound, err error) {
|
||||
unbound.Providers = s.env.CSV("DOT_PROVIDERS")
|
||||
|
||||
unbound.Caching, err = envToBoolPtr("DOT_CACHING")
|
||||
unbound.Caching, err = s.env.BoolPtr("DOT_CACHING")
|
||||
if err != nil {
|
||||
return unbound, fmt.Errorf("environment variable DOT_CACHING: %w", err)
|
||||
return unbound, err
|
||||
}
|
||||
|
||||
unbound.IPv6, err = envToBoolPtr("DOT_IPV6")
|
||||
unbound.IPv6, err = s.env.BoolPtr("DOT_IPV6")
|
||||
if err != nil {
|
||||
return unbound, fmt.Errorf("environment variable DOT_IPV6: %w", err)
|
||||
return unbound, err
|
||||
}
|
||||
|
||||
unbound.VerbosityLevel, err = envToUint8Ptr("DOT_VERBOSITY")
|
||||
unbound.VerbosityLevel, err = s.env.Uint8Ptr("DOT_VERBOSITY")
|
||||
if err != nil {
|
||||
return unbound, fmt.Errorf("environment variable DOT_VERBOSITY: %w", err)
|
||||
return unbound, err
|
||||
}
|
||||
|
||||
unbound.VerbosityDetailsLevel, err = envToUint8Ptr("DOT_VERBOSITY_DETAILS")
|
||||
unbound.VerbosityDetailsLevel, err = s.env.Uint8Ptr("DOT_VERBOSITY_DETAILS")
|
||||
if err != nil {
|
||||
return unbound, fmt.Errorf("environment variable DOT_VERBOSITY_DETAILS: %w", err)
|
||||
return unbound, err
|
||||
}
|
||||
|
||||
unbound.ValidationLogLevel, err = envToUint8Ptr("DOT_VALIDATION_LOGLEVEL")
|
||||
unbound.ValidationLogLevel, err = s.env.Uint8Ptr("DOT_VALIDATION_LOGLEVEL")
|
||||
if err != nil {
|
||||
return unbound, fmt.Errorf("environment variable DOT_VALIDATION_LOGLEVEL: %w", err)
|
||||
return unbound, err
|
||||
}
|
||||
|
||||
return unbound, nil
|
||||
|
||||
26
internal/configuration/sources/env/updater.go
vendored
26
internal/configuration/sources/env/updater.go
vendored
@@ -1,14 +1,11 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
)
|
||||
|
||||
func readUpdater() (updater settings.Updater, err error) {
|
||||
updater.Period, err = readUpdaterPeriod()
|
||||
func (s *Source) readUpdater() (updater settings.Updater, err error) {
|
||||
updater.Period, err = s.env.DurationPtr("UPDATER_PERIOD")
|
||||
if err != nil {
|
||||
return updater, err
|
||||
}
|
||||
@@ -18,29 +15,16 @@ func readUpdater() (updater settings.Updater, err error) {
|
||||
return updater, err
|
||||
}
|
||||
|
||||
updater.MinRatio, err = envToFloat64("UPDATER_MIN_RATIO")
|
||||
updater.MinRatio, err = s.env.Float64("UPDATER_MIN_RATIO")
|
||||
if err != nil {
|
||||
return updater, fmt.Errorf("environment variable UPDATER_MIN_RATIO: %w", err)
|
||||
return updater, err
|
||||
}
|
||||
|
||||
updater.Providers = envToCSV("UPDATER_VPN_SERVICE_PROVIDERS")
|
||||
updater.Providers = s.env.CSV("UPDATER_VPN_SERVICE_PROVIDERS")
|
||||
|
||||
return updater, nil
|
||||
}
|
||||
|
||||
func readUpdaterPeriod() (period *time.Duration, err error) {
|
||||
s := getCleanedEnv("UPDATER_PERIOD")
|
||||
if s == "" {
|
||||
return nil, nil //nolint:nilnil
|
||||
}
|
||||
period = new(time.Duration)
|
||||
*period, err = time.ParseDuration(s)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("environment variable UPDATER_PERIOD: %w", err)
|
||||
}
|
||||
return period, nil
|
||||
}
|
||||
|
||||
func readUpdaterDNSAddress() (address string, err error) {
|
||||
// TODO this is currently using Cloudflare in
|
||||
// plaintext to not be blocked by DNS over TLS by default.
|
||||
|
||||
22
internal/configuration/sources/env/version.go
vendored
22
internal/configuration/sources/env/version.go
vendored
@@ -1,32 +1,14 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/govalid/binary"
|
||||
)
|
||||
|
||||
func readVersion() (version settings.Version, err error) {
|
||||
version.Enabled, err = readVersionEnabled()
|
||||
func (s *Source) readVersion() (version settings.Version, err error) {
|
||||
version.Enabled, err = s.env.BoolPtr("VERSION_INFORMATION")
|
||||
if err != nil {
|
||||
return version, err
|
||||
}
|
||||
|
||||
return version, nil
|
||||
}
|
||||
|
||||
func readVersionEnabled() (enabled *bool, err error) {
|
||||
s := getCleanedEnv("VERSION_INFORMATION")
|
||||
if s == "" {
|
||||
return nil, nil //nolint:nilnil
|
||||
}
|
||||
|
||||
enabled = new(bool)
|
||||
*enabled, err = binary.Validate(s)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("environment variable VERSION_INFORMATION: %w", err)
|
||||
}
|
||||
|
||||
return enabled, nil
|
||||
}
|
||||
|
||||
3
internal/configuration/sources/env/vpn.go
vendored
3
internal/configuration/sources/env/vpn.go
vendored
@@ -2,13 +2,12 @@ package env
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
)
|
||||
|
||||
func (s *Source) readVPN() (vpn settings.VPN, err error) {
|
||||
vpn.Type = strings.ToLower(getCleanedEnv("VPN_TYPE"))
|
||||
vpn.Type = s.env.String("VPN_TYPE")
|
||||
|
||||
vpn.Provider, err = s.readProvider(vpn.Type)
|
||||
if err != nil {
|
||||
|
||||
47
internal/configuration/sources/env/wireguard.go
vendored
47
internal/configuration/sources/env/wireguard.go
vendored
@@ -1,44 +1,33 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gosettings/sources/env"
|
||||
)
|
||||
|
||||
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 = s.getEnvWithRetro("VPN_INTERFACE", "WIREGUARD_INTERFACE")
|
||||
wireguard.Implementation = os.Getenv("WIREGUARD_IMPLEMENTATION")
|
||||
wireguard.Addresses, err = s.readWireguardAddresses()
|
||||
wireguard.PrivateKey = s.env.Get("WIREGUARD_PRIVATE_KEY", env.ForceLowercase(false))
|
||||
wireguard.PreSharedKey = s.env.Get("WIREGUARD_PRESHARED_KEY", env.ForceLowercase(false))
|
||||
wireguard.Interface = s.env.String("VPN_INTERFACE",
|
||||
env.RetroKeys("WIREGUARD_INTERFACE"), env.ForceLowercase(false))
|
||||
wireguard.Implementation = s.env.String("WIREGUARD_IMPLEMENTATION")
|
||||
wireguard.Addresses, err = s.env.CSVNetipPrefixes("WIREGUARD_ADDRESSES",
|
||||
env.RetroKeys("WIREGUARD_ADDRESS"))
|
||||
if err != nil {
|
||||
return wireguard, err // already wrapped
|
||||
}
|
||||
wireguard.AllowedIPs, err = s.env.CSVNetipPrefixes("WIREGUARD_ALLOWED_IPS")
|
||||
if err != nil {
|
||||
return wireguard, err // already wrapped
|
||||
}
|
||||
mtuPtr, err := s.env.Uint16Ptr("WIREGUARD_MTU")
|
||||
if err != nil {
|
||||
return wireguard, err
|
||||
} else if mtuPtr != nil {
|
||||
wireguard.MTU = *mtuPtr
|
||||
}
|
||||
return wireguard, nil
|
||||
}
|
||||
|
||||
func (s *Source) readWireguardAddresses() (addresses []netip.Prefix, err error) {
|
||||
key, addressesCSV := s.getEnvWithRetro("WIREGUARD_ADDRESSES", "WIREGUARD_ADDRESS")
|
||||
if addressesCSV == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
addressStrings := strings.Split(addressesCSV, ",")
|
||||
addresses = make([]netip.Prefix, len(addressStrings))
|
||||
for i, addressString := range addressStrings {
|
||||
addressString = strings.TrimSpace(addressString)
|
||||
addresses[i], err = netip.ParsePrefix(addressString)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("environment variable %s: %w", key, err)
|
||||
}
|
||||
}
|
||||
|
||||
return addresses, nil
|
||||
}
|
||||
|
||||
@@ -1,55 +1,23 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/netip"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/govalid/port"
|
||||
"github.com/qdm12/gosettings/sources/env"
|
||||
)
|
||||
|
||||
func (s *Source) readWireguardSelection() (
|
||||
selection settings.WireguardSelection, err error) {
|
||||
selection.EndpointIP, err = s.readWireguardEndpointIP()
|
||||
selection.EndpointIP, err = s.env.NetipAddr("VPN_ENDPOINT_IP", env.RetroKeys("WIREGUARD_ENDPOINT_IP"))
|
||||
if err != nil {
|
||||
return selection, err
|
||||
}
|
||||
|
||||
selection.EndpointPort, err = s.readWireguardCustomPort()
|
||||
selection.EndpointPort, err = s.env.Uint16Ptr("VPN_ENDPOINT_PORT", env.RetroKeys("WIREGUARD_ENDPOINT_PORT"))
|
||||
if err != nil {
|
||||
return selection, err
|
||||
}
|
||||
|
||||
selection.PublicKey = getCleanedEnv("WIREGUARD_PUBLIC_KEY")
|
||||
selection.PublicKey = s.env.String("WIREGUARD_PUBLIC_KEY", env.ForceLowercase(false))
|
||||
|
||||
return selection, nil
|
||||
}
|
||||
|
||||
func (s *Source) readWireguardEndpointIP() (endpointIP netip.Addr, err error) {
|
||||
key, value := s.getEnvWithRetro("VPN_ENDPOINT_IP", "WIREGUARD_ENDPOINT_IP")
|
||||
if value == "" {
|
||||
return endpointIP, nil
|
||||
}
|
||||
|
||||
endpointIP, err = netip.ParseAddr(value)
|
||||
if err != nil {
|
||||
return endpointIP, fmt.Errorf("environment variable %s: %w", key, err)
|
||||
}
|
||||
|
||||
return endpointIP, nil
|
||||
}
|
||||
|
||||
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(value)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("environment variable %s: %w", key, err)
|
||||
}
|
||||
|
||||
return customPort, nil
|
||||
}
|
||||
|
||||
3
internal/configuration/sources/files/helpers_test.go
Normal file
3
internal/configuration/sources/files/helpers_test.go
Normal file
@@ -0,0 +1,3 @@
|
||||
package files
|
||||
|
||||
func ptrTo[T any](x T) *T { return &x }
|
||||
16
internal/configuration/sources/files/provider.go
Normal file
16
internal/configuration/sources/files/provider.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package files
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
)
|
||||
|
||||
func (s *Source) readProvider() (provider settings.Provider, err error) {
|
||||
provider.ServerSelection, err = s.readServerSelection()
|
||||
if err != nil {
|
||||
return provider, fmt.Errorf("server selection: %w", err)
|
||||
}
|
||||
|
||||
return provider, nil
|
||||
}
|
||||
@@ -4,10 +4,15 @@ import (
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
)
|
||||
|
||||
type Source struct{}
|
||||
type Source struct {
|
||||
wireguardConfigPath string
|
||||
}
|
||||
|
||||
func New() *Source {
|
||||
return &Source{}
|
||||
const wireguardConfigPath = "/gluetun/wireguard/wg0.conf"
|
||||
return &Source{
|
||||
wireguardConfigPath: wireguardConfigPath,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Source) String() string { return "files" }
|
||||
|
||||
16
internal/configuration/sources/files/serverselection.go
Normal file
16
internal/configuration/sources/files/serverselection.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package files
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
)
|
||||
|
||||
func (s *Source) readServerSelection() (selection settings.ServerSelection, err error) {
|
||||
selection.Wireguard, err = s.readWireguardSelection()
|
||||
if err != nil {
|
||||
return selection, fmt.Errorf("wireguard: %w", err)
|
||||
}
|
||||
|
||||
return selection, nil
|
||||
}
|
||||
@@ -7,10 +7,20 @@ import (
|
||||
)
|
||||
|
||||
func (s *Source) readVPN() (vpn settings.VPN, err error) {
|
||||
vpn.Provider, err = s.readProvider()
|
||||
if err != nil {
|
||||
return vpn, fmt.Errorf("provider: %w", err)
|
||||
}
|
||||
|
||||
vpn.OpenVPN, err = s.readOpenVPN()
|
||||
if err != nil {
|
||||
return vpn, fmt.Errorf("OpenVPN: %w", err)
|
||||
}
|
||||
|
||||
vpn.Wireguard, err = s.readWireguard()
|
||||
if err != nil {
|
||||
return vpn, fmt.Errorf("wireguard: %w", err)
|
||||
}
|
||||
|
||||
return vpn, nil
|
||||
}
|
||||
|
||||
114
internal/configuration/sources/files/wireguard.go
Normal file
114
internal/configuration/sources/files/wireguard.go
Normal file
@@ -0,0 +1,114 @@
|
||||
package files
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
"gopkg.in/ini.v1"
|
||||
)
|
||||
|
||||
var (
|
||||
regexINISectionNotExist = regexp.MustCompile(`^section ".+" does not exist$`)
|
||||
regexINIKeyNotExist = regexp.MustCompile(`key ".*" not exists$`)
|
||||
)
|
||||
|
||||
func (s *Source) readWireguard() (wireguard settings.Wireguard, err error) {
|
||||
fileStringPtr, err := ReadFromFile(s.wireguardConfigPath)
|
||||
if err != nil {
|
||||
return wireguard, fmt.Errorf("reading file: %w", err)
|
||||
}
|
||||
|
||||
if fileStringPtr == nil {
|
||||
return wireguard, nil
|
||||
}
|
||||
|
||||
rawData := []byte(*fileStringPtr)
|
||||
iniFile, err := ini.Load(rawData)
|
||||
if err != nil {
|
||||
return wireguard, fmt.Errorf("loading ini from reader: %w", err)
|
||||
}
|
||||
|
||||
interfaceSection, err := iniFile.GetSection("Interface")
|
||||
if err == nil {
|
||||
err = parseWireguardInterfaceSection(interfaceSection, &wireguard)
|
||||
if err != nil {
|
||||
return wireguard, fmt.Errorf("parsing interface section: %w", err)
|
||||
}
|
||||
} else if !regexINISectionNotExist.MatchString(err.Error()) {
|
||||
// can never happen
|
||||
return wireguard, fmt.Errorf("getting interface section: %w", err)
|
||||
}
|
||||
|
||||
return wireguard, nil
|
||||
}
|
||||
|
||||
func parseWireguardInterfaceSection(interfaceSection *ini.Section,
|
||||
wireguard *settings.Wireguard) (err error) {
|
||||
wireguard.PrivateKey, err = parseINIWireguardKey(interfaceSection, "PrivateKey")
|
||||
if err != nil {
|
||||
return err // error is already wrapped correctly
|
||||
}
|
||||
|
||||
wireguard.PreSharedKey, err = parseINIWireguardKey(interfaceSection, "PreSharedKey")
|
||||
if err != nil {
|
||||
return err // error is already wrapped correctly
|
||||
}
|
||||
|
||||
wireguard.Addresses, err = parseINIWireguardAddress(interfaceSection)
|
||||
if err != nil {
|
||||
return err // error is already wrapped correctly
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseINIWireguardKey(section *ini.Section, keyName string) (
|
||||
key *string, err error) {
|
||||
iniKey, err := section.GetKey(keyName)
|
||||
if err != nil {
|
||||
if regexINIKeyNotExist.MatchString(err.Error()) {
|
||||
return nil, nil //nolint:nilnil
|
||||
}
|
||||
// can never happen
|
||||
return nil, fmt.Errorf("getting %s key: %w", keyName, err)
|
||||
}
|
||||
|
||||
key = new(string)
|
||||
*key = iniKey.String()
|
||||
_, err = wgtypes.ParseKey(*key)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing %s: %s: %w", keyName, *key, err)
|
||||
}
|
||||
return key, nil
|
||||
}
|
||||
|
||||
func parseINIWireguardAddress(section *ini.Section) (
|
||||
addresses []netip.Prefix, err error) {
|
||||
addressKey, err := section.GetKey("Address")
|
||||
if err != nil {
|
||||
if regexINIKeyNotExist.MatchString(err.Error()) {
|
||||
return nil, nil
|
||||
}
|
||||
// can never happen
|
||||
return nil, fmt.Errorf("getting Address key: %w", err)
|
||||
}
|
||||
|
||||
addressStrings := strings.Split(addressKey.String(), ",")
|
||||
addresses = make([]netip.Prefix, len(addressStrings))
|
||||
for i, addressString := range addressStrings {
|
||||
addressString = strings.TrimSpace(addressString)
|
||||
if !strings.ContainsRune(addressString, '/') {
|
||||
addressString += "/32"
|
||||
}
|
||||
addresses[i], err = netip.ParsePrefix(addressString)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing address: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return addresses, nil
|
||||
}
|
||||
273
internal/configuration/sources/files/wireguard_test.go
Normal file
273
internal/configuration/sources/files/wireguard_test.go
Normal file
@@ -0,0 +1,273 @@
|
||||
package files
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/ini.v1"
|
||||
)
|
||||
|
||||
func Test_Source_readWireguard(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("fail reading from file", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dirPath := t.TempDir()
|
||||
source := &Source{
|
||||
wireguardConfigPath: dirPath,
|
||||
}
|
||||
wireguard, err := source.readWireguard()
|
||||
assert.Equal(t, settings.Wireguard{}, wireguard)
|
||||
assert.Error(t, err)
|
||||
assert.Regexp(t, `reading file: read .+: is a directory`, err.Error())
|
||||
})
|
||||
|
||||
t.Run("no file", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
noFile := filepath.Join(t.TempDir(), "doesnotexist")
|
||||
source := &Source{
|
||||
wireguardConfigPath: noFile,
|
||||
}
|
||||
wireguard, err := source.readWireguard()
|
||||
assert.Equal(t, settings.Wireguard{}, wireguard)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
testCases := map[string]struct {
|
||||
fileContent string
|
||||
wireguard settings.Wireguard
|
||||
errMessage string
|
||||
}{
|
||||
"ini load error": {
|
||||
fileContent: "invalid",
|
||||
errMessage: "loading ini from reader: key-value delimiter not found: invalid",
|
||||
},
|
||||
"empty file": {},
|
||||
"interface section parsing error": {
|
||||
fileContent: `
|
||||
[Interface]
|
||||
PrivateKey = x
|
||||
`,
|
||||
errMessage: "parsing interface section: parsing PrivateKey: " +
|
||||
"x: wgtypes: failed to parse base64-encoded key: " +
|
||||
"illegal base64 data at input byte 0",
|
||||
},
|
||||
"success": {
|
||||
fileContent: `
|
||||
[Interface]
|
||||
PrivateKey = QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8=
|
||||
PreSharedKey = YJ680VN+dGrdsWNjSFqZ6vvwuiNhbq502ZL3G7Q3o3g=
|
||||
Address = 10.38.22.35/32
|
||||
DNS = 193.138.218.74
|
||||
|
||||
[Peer]
|
||||
`,
|
||||
wireguard: settings.Wireguard{
|
||||
PrivateKey: ptrTo("QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8="),
|
||||
PreSharedKey: ptrTo("YJ680VN+dGrdsWNjSFqZ6vvwuiNhbq502ZL3G7Q3o3g="),
|
||||
Addresses: []netip.Prefix{
|
||||
netip.PrefixFrom(netip.AddrFrom4([4]byte{10, 38, 22, 35}), 32),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for testName, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run(testName, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
configFile := filepath.Join(t.TempDir(), "wg.conf")
|
||||
err := os.WriteFile(configFile, []byte(testCase.fileContent), 0600)
|
||||
require.NoError(t, err)
|
||||
|
||||
source := &Source{
|
||||
wireguardConfigPath: configFile,
|
||||
}
|
||||
|
||||
wireguard, err := source.readWireguard()
|
||||
|
||||
assert.Equal(t, testCase.wireguard, wireguard)
|
||||
if testCase.errMessage != "" {
|
||||
assert.EqualError(t, err, testCase.errMessage)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_parseWireguardInterfaceSection(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := map[string]struct {
|
||||
iniData string
|
||||
wireguard settings.Wireguard
|
||||
errMessage string
|
||||
}{
|
||||
"private key error": {
|
||||
iniData: `[Interface]
|
||||
PrivateKey = x`,
|
||||
errMessage: "parsing PrivateKey: x: " +
|
||||
"wgtypes: failed to parse base64-encoded key: " +
|
||||
"illegal base64 data at input byte 0",
|
||||
},
|
||||
"pre shared key error": {
|
||||
iniData: `[Interface]
|
||||
PreSharedKey = x
|
||||
`,
|
||||
errMessage: "parsing PreSharedKey: x: " +
|
||||
"wgtypes: failed to parse base64-encoded key: " +
|
||||
"illegal base64 data at input byte 0",
|
||||
},
|
||||
"address error": {
|
||||
iniData: `[Interface]
|
||||
Address = x
|
||||
`,
|
||||
errMessage: "parsing address: netip.ParsePrefix(\"x/32\"): ParseAddr(\"x\"): unable to parse IP",
|
||||
},
|
||||
"success": {
|
||||
iniData: `
|
||||
[Interface]
|
||||
PrivateKey = QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8=
|
||||
PreSharedKey = YJ680VN+dGrdsWNjSFqZ6vvwuiNhbq502ZL3G7Q3o3g=
|
||||
Address = 10.38.22.35/32
|
||||
`,
|
||||
wireguard: settings.Wireguard{
|
||||
PrivateKey: ptrTo("QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8="),
|
||||
PreSharedKey: ptrTo("YJ680VN+dGrdsWNjSFqZ6vvwuiNhbq502ZL3G7Q3o3g="),
|
||||
Addresses: []netip.Prefix{
|
||||
netip.PrefixFrom(netip.AddrFrom4([4]byte{10, 38, 22, 35}), 32),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for testName, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run(testName, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
iniFile, err := ini.Load([]byte(testCase.iniData))
|
||||
require.NoError(t, err)
|
||||
iniSection, err := iniFile.GetSection("Interface")
|
||||
require.NoError(t, err)
|
||||
|
||||
var wireguard settings.Wireguard
|
||||
err = parseWireguardInterfaceSection(iniSection, &wireguard)
|
||||
|
||||
assert.Equal(t, testCase.wireguard, wireguard)
|
||||
if testCase.errMessage != "" {
|
||||
assert.EqualError(t, err, testCase.errMessage)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_parseINIWireguardKey(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := map[string]struct {
|
||||
fileContent string
|
||||
keyName string
|
||||
key *string
|
||||
errMessage string
|
||||
}{
|
||||
"key does not exist": {
|
||||
fileContent: `[Interface]`,
|
||||
keyName: "PrivateKey",
|
||||
},
|
||||
"bad Wireguard key": {
|
||||
fileContent: `[Interface]
|
||||
PrivateKey = x`,
|
||||
keyName: "PrivateKey",
|
||||
errMessage: "parsing PrivateKey: x: " +
|
||||
"wgtypes: failed to parse base64-encoded key: " +
|
||||
"illegal base64 data at input byte 0",
|
||||
},
|
||||
"success": {
|
||||
fileContent: `[Interface]
|
||||
PrivateKey = QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8=`,
|
||||
keyName: "PrivateKey",
|
||||
key: ptrTo("QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8="),
|
||||
},
|
||||
}
|
||||
|
||||
for testName, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run(testName, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
iniFile, err := ini.Load([]byte(testCase.fileContent))
|
||||
require.NoError(t, err)
|
||||
iniSection, err := iniFile.GetSection("Interface")
|
||||
require.NoError(t, err)
|
||||
|
||||
key, err := parseINIWireguardKey(iniSection, testCase.keyName)
|
||||
|
||||
assert.Equal(t, testCase.key, key)
|
||||
if testCase.errMessage != "" {
|
||||
assert.EqualError(t, err, testCase.errMessage)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_parseINIWireguardAddress(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := map[string]struct {
|
||||
fileContent string
|
||||
addresses []netip.Prefix
|
||||
errMessage string
|
||||
}{
|
||||
"key does not exist": {
|
||||
fileContent: `[Interface]`,
|
||||
},
|
||||
"bad address": {
|
||||
fileContent: `[Interface]
|
||||
Address = x`,
|
||||
errMessage: "parsing address: netip.ParsePrefix(\"x/32\"): ParseAddr(\"x\"): unable to parse IP",
|
||||
},
|
||||
"success": {
|
||||
fileContent: `[Interface]
|
||||
Address = 1.2.3.4/32, 5.6.7.8/32`,
|
||||
addresses: []netip.Prefix{
|
||||
netip.PrefixFrom(netip.AddrFrom4([4]byte{1, 2, 3, 4}), 32),
|
||||
netip.PrefixFrom(netip.AddrFrom4([4]byte{5, 6, 7, 8}), 32),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for testName, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run(testName, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
iniFile, err := ini.Load([]byte(testCase.fileContent))
|
||||
require.NoError(t, err)
|
||||
iniSection, err := iniFile.GetSection("Interface")
|
||||
require.NoError(t, err)
|
||||
|
||||
addresses, err := parseINIWireguardAddress(iniSection)
|
||||
|
||||
assert.Equal(t, testCase.addresses, addresses)
|
||||
if testCase.errMessage != "" {
|
||||
assert.EqualError(t, err, testCase.errMessage)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
83
internal/configuration/sources/files/wireguardselection.go
Normal file
83
internal/configuration/sources/files/wireguardselection.go
Normal file
@@ -0,0 +1,83 @@
|
||||
package files
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/govalid/port"
|
||||
"gopkg.in/ini.v1"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrEndpointHostNotIP = errors.New("endpoint host is not an IP")
|
||||
)
|
||||
|
||||
func (s *Source) readWireguardSelection() (selection settings.WireguardSelection, err error) {
|
||||
fileStringPtr, err := ReadFromFile(s.wireguardConfigPath)
|
||||
if err != nil {
|
||||
return selection, fmt.Errorf("reading file: %w", err)
|
||||
}
|
||||
|
||||
if fileStringPtr == nil {
|
||||
return selection, nil
|
||||
}
|
||||
|
||||
rawData := []byte(*fileStringPtr)
|
||||
iniFile, err := ini.Load(rawData)
|
||||
if err != nil {
|
||||
return selection, fmt.Errorf("loading ini from reader: %w", err)
|
||||
}
|
||||
|
||||
peerSection, err := iniFile.GetSection("Peer")
|
||||
if err == nil {
|
||||
err = parseWireguardPeerSection(peerSection, &selection)
|
||||
if err != nil {
|
||||
return selection, fmt.Errorf("parsing peer section: %w", err)
|
||||
}
|
||||
} else if !regexINISectionNotExist.MatchString(err.Error()) {
|
||||
// can never happen
|
||||
return selection, fmt.Errorf("getting peer section: %w", err)
|
||||
}
|
||||
|
||||
return selection, nil
|
||||
}
|
||||
|
||||
func parseWireguardPeerSection(peerSection *ini.Section,
|
||||
selection *settings.WireguardSelection) (err error) {
|
||||
publicKeyPtr, err := parseINIWireguardKey(peerSection, "PublicKey")
|
||||
if err != nil {
|
||||
return err // error is already wrapped correctly
|
||||
} else if publicKeyPtr != nil {
|
||||
selection.PublicKey = *publicKeyPtr
|
||||
}
|
||||
|
||||
endpointKey, err := peerSection.GetKey("Endpoint")
|
||||
if err == nil {
|
||||
endpoint := endpointKey.String()
|
||||
host, portString, err := net.SplitHostPort(endpoint)
|
||||
if err != nil {
|
||||
return fmt.Errorf("splitting endpoint: %w", err)
|
||||
}
|
||||
|
||||
ip, err := netip.ParseAddr(host)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %w", ErrEndpointHostNotIP, err)
|
||||
}
|
||||
|
||||
endpointPort, err := port.Validate(portString)
|
||||
if err != nil {
|
||||
return fmt.Errorf("port from Endpoint key: %w", err)
|
||||
}
|
||||
|
||||
selection.EndpointIP = ip
|
||||
selection.EndpointPort = &endpointPort
|
||||
} else if !regexINIKeyNotExist.MatchString(err.Error()) {
|
||||
// can never happen
|
||||
return fmt.Errorf("getting endpoint key: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
181
internal/configuration/sources/files/wireguardselection_test.go
Normal file
181
internal/configuration/sources/files/wireguardselection_test.go
Normal file
@@ -0,0 +1,181 @@
|
||||
package files
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/ini.v1"
|
||||
)
|
||||
|
||||
func uint16Ptr(n uint16) *uint16 { return &n }
|
||||
|
||||
func Test_Source_readWireguardSelection(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("fail reading from file", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dirPath := t.TempDir()
|
||||
source := &Source{
|
||||
wireguardConfigPath: dirPath,
|
||||
}
|
||||
wireguard, err := source.readWireguardSelection()
|
||||
assert.Equal(t, settings.WireguardSelection{}, wireguard)
|
||||
assert.Error(t, err)
|
||||
assert.Regexp(t, `reading file: read .+: is a directory`, err.Error())
|
||||
})
|
||||
|
||||
t.Run("no file", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
noFile := filepath.Join(t.TempDir(), "doesnotexist")
|
||||
source := &Source{
|
||||
wireguardConfigPath: noFile,
|
||||
}
|
||||
wireguard, err := source.readWireguardSelection()
|
||||
assert.Equal(t, settings.WireguardSelection{}, wireguard)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
testCases := map[string]struct {
|
||||
fileContent string
|
||||
selection settings.WireguardSelection
|
||||
errMessage string
|
||||
}{
|
||||
"ini load error": {
|
||||
fileContent: "invalid",
|
||||
errMessage: "loading ini from reader: key-value delimiter not found: invalid",
|
||||
},
|
||||
"empty file": {},
|
||||
"peer section parsing error": {
|
||||
fileContent: `
|
||||
[Peer]
|
||||
PublicKey = x
|
||||
`,
|
||||
errMessage: "parsing peer section: parsing PublicKey: " +
|
||||
"x: wgtypes: failed to parse base64-encoded key: " +
|
||||
"illegal base64 data at input byte 0",
|
||||
},
|
||||
"success": {
|
||||
fileContent: `
|
||||
[Peer]
|
||||
PublicKey = QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8=
|
||||
Endpoint = 1.2.3.4:51820
|
||||
`,
|
||||
selection: settings.WireguardSelection{
|
||||
PublicKey: "QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8=",
|
||||
EndpointIP: netip.AddrFrom4([4]byte{1, 2, 3, 4}),
|
||||
EndpointPort: uint16Ptr(51820),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for testName, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run(testName, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
configFile := filepath.Join(t.TempDir(), "wg.conf")
|
||||
err := os.WriteFile(configFile, []byte(testCase.fileContent), 0600)
|
||||
require.NoError(t, err)
|
||||
|
||||
source := &Source{
|
||||
wireguardConfigPath: configFile,
|
||||
}
|
||||
|
||||
wireguard, err := source.readWireguardSelection()
|
||||
|
||||
assert.Equal(t, testCase.selection, wireguard)
|
||||
if testCase.errMessage != "" {
|
||||
assert.EqualError(t, err, testCase.errMessage)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_parseWireguardPeerSection(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := map[string]struct {
|
||||
iniData string
|
||||
selection settings.WireguardSelection
|
||||
errMessage string
|
||||
}{
|
||||
"public key error": {
|
||||
iniData: `[Peer]
|
||||
PublicKey = x`,
|
||||
errMessage: "parsing PublicKey: x: " +
|
||||
"wgtypes: failed to parse base64-encoded key: " +
|
||||
"illegal base64 data at input byte 0",
|
||||
},
|
||||
"public key set": {
|
||||
iniData: `[Peer]
|
||||
PublicKey = QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8=`,
|
||||
selection: settings.WireguardSelection{
|
||||
PublicKey: "QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8=",
|
||||
},
|
||||
},
|
||||
"missing port in endpoint": {
|
||||
iniData: `[Peer]
|
||||
Endpoint = x`,
|
||||
errMessage: "splitting endpoint: address x: missing port in address",
|
||||
},
|
||||
"endpoint host is not IP": {
|
||||
iniData: `[Peer]
|
||||
Endpoint = website.com:51820`,
|
||||
errMessage: "endpoint host is not an IP: ParseAddr(\"website.com\"): unexpected character (at \"website.com\")",
|
||||
},
|
||||
"endpoint port is not valid": {
|
||||
iniData: `[Peer]
|
||||
Endpoint = 1.2.3.4:518299`,
|
||||
errMessage: "port from Endpoint key: port cannot be higher than 65535: 518299",
|
||||
},
|
||||
"valid endpoint": {
|
||||
iniData: `[Peer]
|
||||
Endpoint = 1.2.3.4:51820`,
|
||||
selection: settings.WireguardSelection{
|
||||
EndpointIP: netip.AddrFrom4([4]byte{1, 2, 3, 4}),
|
||||
EndpointPort: uint16Ptr(51820),
|
||||
},
|
||||
},
|
||||
"all set": {
|
||||
iniData: `[Peer]
|
||||
PublicKey = QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8=
|
||||
Endpoint = 1.2.3.4:51820`,
|
||||
selection: settings.WireguardSelection{
|
||||
PublicKey: "QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8=",
|
||||
EndpointIP: netip.AddrFrom4([4]byte{1, 2, 3, 4}),
|
||||
EndpointPort: uint16Ptr(51820),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for testName, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run(testName, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
iniFile, err := ini.Load([]byte(testCase.iniData))
|
||||
require.NoError(t, err)
|
||||
iniSection, err := iniFile.GetSection("Peer")
|
||||
require.NoError(t, err)
|
||||
|
||||
var selection settings.WireguardSelection
|
||||
err = parseWireguardPeerSection(iniSection, &selection)
|
||||
|
||||
assert.Equal(t, testCase.selection, selection)
|
||||
if testCase.errMessage != "" {
|
||||
assert.EqualError(t, err, testCase.errMessage)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -2,35 +2,24 @@ package secrets
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/sources/files"
|
||||
"github.com/qdm12/gluetun/internal/openvpn/extract"
|
||||
"github.com/qdm12/gosettings/sources/env"
|
||||
)
|
||||
|
||||
// getCleanedEnv returns an environment variable value with
|
||||
// surrounding spaces and trailing new line characters removed.
|
||||
func getCleanedEnv(envKey string) (value string) {
|
||||
value = os.Getenv(envKey)
|
||||
value = strings.TrimSpace(value)
|
||||
value = strings.TrimSuffix(value, "\r\n")
|
||||
value = strings.TrimSuffix(value, "\n")
|
||||
return value
|
||||
}
|
||||
|
||||
func readSecretFileAsStringPtr(secretPathEnvKey, defaultSecretPath string) (
|
||||
func (s *Source) readSecretFileAsStringPtr(secretPathEnvKey, defaultSecretPath string) (
|
||||
stringPtr *string, err error) {
|
||||
path := getCleanedEnv(secretPathEnvKey)
|
||||
path := s.env.String(secretPathEnvKey, env.ForceLowercase(false))
|
||||
if path == "" {
|
||||
path = defaultSecretPath
|
||||
}
|
||||
return files.ReadFromFile(path)
|
||||
}
|
||||
|
||||
func readPEMSecretFile(secretPathEnvKey, defaultSecretPath string) (
|
||||
func (s *Source) readPEMSecretFile(secretPathEnvKey, defaultSecretPath string) (
|
||||
base64Ptr *string, err error) {
|
||||
pemData, err := readSecretFileAsStringPtr(secretPathEnvKey, defaultSecretPath)
|
||||
pemData, err := s.readSecretFileAsStringPtr(secretPathEnvKey, defaultSecretPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading secret file: %w", err)
|
||||
}
|
||||
|
||||
92
internal/configuration/sources/secrets/helpers_test.go
Normal file
92
internal/configuration/sources/secrets/helpers_test.go
Normal file
@@ -0,0 +1,92 @@
|
||||
package secrets
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/qdm12/gosettings/sources/env"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func ptrTo[T any](value T) *T { return &value }
|
||||
|
||||
func Test_readSecretFileAsStringPtr(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := map[string]struct {
|
||||
source func(tempDir string) Source
|
||||
secretPathEnvKey string
|
||||
defaultSecretFileName string
|
||||
setupFile func(tempDir string) error
|
||||
stringPtr *string
|
||||
errWrapped error
|
||||
errMessage string
|
||||
}{
|
||||
"no_secret_file": {
|
||||
defaultSecretFileName: "default_secret_file",
|
||||
secretPathEnvKey: "SECRET_FILE",
|
||||
},
|
||||
"empty_secret_file": {
|
||||
defaultSecretFileName: "default_secret_file",
|
||||
secretPathEnvKey: "SECRET_FILE",
|
||||
setupFile: func(tempDir string) error {
|
||||
secretFilepath := filepath.Join(tempDir, "default_secret_file")
|
||||
return os.WriteFile(secretFilepath, nil, os.ModePerm)
|
||||
},
|
||||
stringPtr: ptrTo(""),
|
||||
},
|
||||
"default_secret_file": {
|
||||
defaultSecretFileName: "default_secret_file",
|
||||
secretPathEnvKey: "SECRET_FILE",
|
||||
setupFile: func(tempDir string) error {
|
||||
secretFilepath := filepath.Join(tempDir, "default_secret_file")
|
||||
return os.WriteFile(secretFilepath, []byte("A"), os.ModePerm)
|
||||
},
|
||||
stringPtr: ptrTo("A"),
|
||||
},
|
||||
"env_specified_secret_file": {
|
||||
source: func(tempDir string) Source {
|
||||
secretFilepath := filepath.Join(tempDir, "secret_file")
|
||||
environ := []string{"SECRET_FILE=" + secretFilepath}
|
||||
return Source{env: *env.New(environ, nil)}
|
||||
},
|
||||
defaultSecretFileName: "default_secret_file",
|
||||
secretPathEnvKey: "SECRET_FILE",
|
||||
setupFile: func(tempDir string) error {
|
||||
secretFilepath := filepath.Join(tempDir, "secret_file")
|
||||
return os.WriteFile(secretFilepath, []byte("B"), os.ModePerm)
|
||||
},
|
||||
stringPtr: ptrTo("B"),
|
||||
},
|
||||
}
|
||||
|
||||
for name, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
tempDir := t.TempDir()
|
||||
|
||||
var source Source
|
||||
if testCase.source != nil {
|
||||
source = testCase.source(tempDir)
|
||||
}
|
||||
|
||||
defaultSecretPath := filepath.Join(tempDir, testCase.defaultSecretFileName)
|
||||
if testCase.setupFile != nil {
|
||||
err := testCase.setupFile(tempDir)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
stringPtr, err := source.readSecretFileAsStringPtr(
|
||||
testCase.secretPathEnvKey, defaultSecretPath)
|
||||
|
||||
assert.Equal(t, testCase.stringPtr, stringPtr)
|
||||
assert.ErrorIs(t, err, testCase.errWrapped)
|
||||
if testCase.errWrapped != nil {
|
||||
assert.EqualError(t, err, testCase.errMessage)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user