Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4b86f29f59 | ||
|
|
12eafa0f65 | ||
|
|
7acca7bc10 | ||
|
|
5cc55c92fa |
@@ -1,2 +1,2 @@
|
|||||||
FROM qmcgaw/godevcontainer
|
FROM qmcgaw/godevcontainer
|
||||||
RUN apk add wireguard-tools htop openssl
|
RUN apk add wireguard-tools
|
||||||
|
|||||||
@@ -9,21 +9,17 @@ It works on Linux, Windows and OSX.
|
|||||||
- [VS code](https://code.visualstudio.com/download) installed
|
- [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
|
- [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
|
- [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
|
- [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
|
## 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. 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. 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
|
## Customization
|
||||||
|
|
||||||
@@ -33,9 +29,13 @@ You can make changes to the [Dockerfile](Dockerfile) and then rebuild the image.
|
|||||||
|
|
||||||
```Dockerfile
|
```Dockerfile
|
||||||
FROM qmcgaw/godevcontainer
|
FROM qmcgaw/godevcontainer
|
||||||
|
USER root
|
||||||
RUN apk add curl
|
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:
|
To rebuild the image, either:
|
||||||
|
|
||||||
- With VSCode through the command palette, select `Remote-Containers: Rebuild and reopen in container`
|
- 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
|
### Entrypoint script
|
||||||
|
|
||||||
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).
|
You can bind mount a shell script to `/home/vscode/.welcome.sh` to replace the [current welcome script](shell/.welcome.sh).
|
||||||
|
|
||||||
### Publish a port
|
### Publish a port
|
||||||
|
|
||||||
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.
|
To access a port from your host to your development container, publish a port in [docker-compose.yml](docker-compose.yml).
|
||||||
|
|
||||||
### Run other services
|
### Run other services
|
||||||
|
|
||||||
|
|||||||
@@ -1,73 +1,81 @@
|
|||||||
{
|
{
|
||||||
"name": "gluetun-dev",
|
"name": "gluetun-dev",
|
||||||
"dockerComposeFile": [
|
"dockerComposeFile": [
|
||||||
"docker-compose.yml"
|
"docker-compose.yml"
|
||||||
],
|
],
|
||||||
"service": "vscode",
|
"service": "vscode",
|
||||||
"runServices": [
|
"runServices": [
|
||||||
"vscode"
|
"vscode"
|
||||||
],
|
],
|
||||||
"shutdownAction": "stopCompose",
|
"shutdownAction": "stopCompose",
|
||||||
"postCreateCommand": "~/.windows.sh && go mod download && go mod tidy",
|
"postCreateCommand": "~/.windows.sh && go mod download && go mod tidy",
|
||||||
"workspaceFolder": "/workspace",
|
"workspaceFolder": "/workspace",
|
||||||
// "overrideCommand": "",
|
"extensions": [
|
||||||
"customizations": {
|
"golang.go",
|
||||||
"vscode": {
|
"eamodio.gitlens", // IDE Git information
|
||||||
"extensions": [
|
"davidanson.vscode-markdownlint",
|
||||||
"golang.go",
|
"ms-azuretools.vscode-docker", // Docker integration and linting
|
||||||
"eamodio.gitlens", // IDE Git information
|
"shardulm94.trailing-spaces", // Show trailing spaces
|
||||||
"davidanson.vscode-markdownlint",
|
"Gruntfuggly.todo-tree", // Highlights TODO comments
|
||||||
"ms-azuretools.vscode-docker", // Docker integration and linting
|
"bierner.emojisense", // Emoji sense for markdown
|
||||||
"shardulm94.trailing-spaces", // Show trailing spaces
|
"stkb.rewrap", // rewrap comments after n characters on one line
|
||||||
"Gruntfuggly.todo-tree", // Highlights TODO comments
|
"vscode-icons-team.vscode-icons", // Better file extension icons
|
||||||
"bierner.emojisense", // Emoji sense for markdown
|
"github.vscode-pull-request-github", // Github interaction
|
||||||
"stkb.rewrap", // rewrap comments after n characters on one line
|
"redhat.vscode-yaml", // Kubernetes, Drone syntax highlighting
|
||||||
"vscode-icons-team.vscode-icons", // Better file extension icons
|
"bajdzis.vscode-database", // Supports connections to mysql or postgres, over SSL, socked
|
||||||
"github.vscode-pull-request-github", // Github interaction
|
"IBM.output-colorizer", // Colorize your output/test logs
|
||||||
"redhat.vscode-yaml", // Kubernetes, Drone syntax highlighting
|
"mohsen1.prettify-json", // Prettify JSON data
|
||||||
"bajdzis.vscode-database", // Supports connections to mysql or postgres, over SSL, socked
|
],
|
||||||
"IBM.output-colorizer", // Colorize your output/test logs
|
"settings": {
|
||||||
"github.copilot" // AI code completion
|
"files.eol": "\n",
|
||||||
],
|
"remote.extensionKind": {
|
||||||
"settings": {
|
"ms-azuretools.vscode-docker": "workspace"
|
||||||
"files.eol": "\n",
|
},
|
||||||
"remote.extensionKind": {
|
"editor.codeActionsOnSaveTimeout": 3000,
|
||||||
"ms-azuretools.vscode-docker": "workspace"
|
"go.useLanguageServer": true,
|
||||||
},
|
"[go]": {
|
||||||
"go.useLanguageServer": true,
|
"editor.formatOnSave": true,
|
||||||
"[go]": {
|
"editor.codeActionsOnSave": {
|
||||||
"editor.codeActionsOnSave": {
|
"source.organizeImports": true,
|
||||||
"source.organizeImports": true
|
},
|
||||||
}
|
// Optional: Disable snippets, as they conflict with completion ranking.
|
||||||
},
|
"editor.snippetSuggestions": "none"
|
||||||
"[go.mod]": {
|
},
|
||||||
"editor.codeActionsOnSave": {
|
"[go.mod]": {
|
||||||
"source.organizeImports": true
|
"editor.formatOnSave": true,
|
||||||
}
|
"editor.codeActionsOnSave": {
|
||||||
},
|
"source.organizeImports": true,
|
||||||
"gopls": {
|
},
|
||||||
"usePlaceholders": false,
|
},
|
||||||
"staticcheck": true
|
"gopls": {
|
||||||
},
|
"usePlaceholders": false,
|
||||||
"go.lintTool": "golangci-lint",
|
"staticcheck": true
|
||||||
"go.lintOnSave": "package",
|
},
|
||||||
"editor.formatOnSave": true,
|
"go.autocompleteUnimportedPackages": true,
|
||||||
"go.buildTags": "linux",
|
"go.gotoSymbol.includeImports": true,
|
||||||
"go.toolsEnvVars": {
|
"go.gotoSymbol.includeGoroot": true,
|
||||||
"CGO_ENABLED": "0"
|
"go.lintTool": "golangci-lint",
|
||||||
},
|
"go.buildOnSave": "workspace",
|
||||||
"go.testEnvVars": {
|
"go.lintOnSave": "workspace",
|
||||||
"CGO_ENABLED": "1"
|
"go.vetOnSave": "workspace",
|
||||||
},
|
"editor.formatOnSave": true,
|
||||||
"go.testFlags": [
|
"go.toolsEnvVars": {
|
||||||
"-v",
|
"GOFLAGS": "-tags=",
|
||||||
"-race"
|
// "CGO_ENABLED": 1 // for the race detector
|
||||||
],
|
},
|
||||||
"go.testTimeout": "10s",
|
"gopls.env": {
|
||||||
"go.coverOnSingleTest": true,
|
"GOFLAGS": "-tags="
|
||||||
"go.coverOnSingleTestFile": true,
|
},
|
||||||
"go.coverOnTestPackage": true
|
"go.testEnvVars": {
|
||||||
}
|
"": ""
|
||||||
}
|
},
|
||||||
}
|
"go.testFlags": [
|
||||||
|
"-v",
|
||||||
|
// "-race"
|
||||||
|
],
|
||||||
|
"go.testTimeout": "10s",
|
||||||
|
"go.coverOnSingleTest": true,
|
||||||
|
"go.coverOnSingleTestFile": true,
|
||||||
|
"go.coverOnTestPackage": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,28 +1,33 @@
|
|||||||
version: "3.7"
|
version: "3.7"
|
||||||
|
|
||||||
services:
|
services:
|
||||||
vscode:
|
vscode:
|
||||||
build: .
|
build: .
|
||||||
volumes:
|
image: godevcontainer
|
||||||
- ../:/workspace
|
devices:
|
||||||
# Docker socket to access Docker server
|
- /dev/net/tun:/dev/net/tun
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
volumes:
|
||||||
# SSH directory for Linux, OSX and WSL
|
- ../:/workspace
|
||||||
# On Linux and OSX, a symlink /mnt/ssh <-> ~/.ssh is
|
# Docker socket to access Docker server
|
||||||
# created in the container. On Windows, files are copied
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
# from /mnt/ssh to ~/.ssh to fix permissions.
|
# Docker configuration
|
||||||
- ~/.ssh:/mnt/ssh
|
- ~/.docker:/root/.docker:z
|
||||||
# Shell history persistence
|
# SSH directory for Linux, OSX and WSL
|
||||||
- ~/.zsh_history:/root/.zsh_history
|
- ~/.ssh:/root/.ssh:z
|
||||||
# Git config
|
# For Windows without WSL, a copy will be made
|
||||||
- ~/.gitconfig:/root/.gitconfig
|
# from /tmp/.ssh to ~/.ssh to fix permissions
|
||||||
environment:
|
#- ~/.ssh:/tmp/.ssh:ro
|
||||||
- TZ=
|
# Shell history persistence
|
||||||
cap_add:
|
- ~/.zsh_history:/root/.zsh_history:z
|
||||||
# For debugging with dlv
|
# Git config
|
||||||
- SYS_PTRACE
|
- ~/.gitconfig:/root/.gitconfig:z
|
||||||
- NET_ADMIN
|
environment:
|
||||||
security_opt:
|
- TZ=
|
||||||
# For debugging with dlv
|
cap_add:
|
||||||
- seccomp:unconfined
|
# For debugging with dlv
|
||||||
entrypoint: [ "zsh", "-c", "while sleep 1000; do :; done" ]
|
# - 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
|
## Resources
|
||||||
|
|
||||||
- [Gluetun guide on development](https://github.com/qdm12/gluetun-wiki/blob/main/contributing/development.md)
|
- [Gluetun guide on development](https://github.com/qdm12/gluetun/wiki/Development)
|
||||||
- [Using Pull Requests](https://help.github.com/articles/about-pull-requests/)
|
- [Using Pull Requests](https://help.github.com/articles/about-pull-requests/)
|
||||||
- [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/)
|
- [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/)
|
||||||
|
|||||||
17
.github/ISSUE_TEMPLATE/bug.yml
vendored
17
.github/ISSUE_TEMPLATE/bug.yml
vendored
@@ -7,18 +7,13 @@ body:
|
|||||||
attributes:
|
attributes:
|
||||||
value: |
|
value: |
|
||||||
Thanks for taking the time to fill out this bug report!
|
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
|
- type: dropdown
|
||||||
id: urgent
|
id: urgent
|
||||||
attributes:
|
attributes:
|
||||||
label: Is this urgent?
|
label: Is this urgent?
|
||||||
description: |
|
description: |
|
||||||
Is this a critical bug, or do you need this fixed urgently?
|
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/blob/main/setup/docker-image-tags.md) if that can help.
|
If this is urgent, note you can use one of the [image tags available](https://github.com/qdm12/gluetun/wiki/Docker-image-tags) if that can help.
|
||||||
options:
|
options:
|
||||||
- "No"
|
- "No"
|
||||||
- "Yes"
|
- "Yes"
|
||||||
@@ -45,7 +40,6 @@ body:
|
|||||||
attributes:
|
attributes:
|
||||||
label: VPN service provider
|
label: VPN service provider
|
||||||
options:
|
options:
|
||||||
- AirVPN
|
|
||||||
- Custom
|
- Custom
|
||||||
- Cyberghost
|
- Cyberghost
|
||||||
- ExpressVPN
|
- ExpressVPN
|
||||||
@@ -60,10 +54,8 @@ body:
|
|||||||
- PrivateVPN
|
- PrivateVPN
|
||||||
- ProtonVPN
|
- ProtonVPN
|
||||||
- PureVPN
|
- PureVPN
|
||||||
- SlickVPN
|
|
||||||
- Surfshark
|
- Surfshark
|
||||||
- TorGuard
|
- TorGuard
|
||||||
- VPNSecure.me
|
|
||||||
- VPNUnlimited
|
- VPNUnlimited
|
||||||
- VyprVPN
|
- VyprVPN
|
||||||
- WeVPN
|
- WeVPN
|
||||||
@@ -80,7 +72,6 @@ body:
|
|||||||
- Portainer
|
- Portainer
|
||||||
- Kubernetes
|
- Kubernetes
|
||||||
- Podman
|
- Podman
|
||||||
- Unraid
|
|
||||||
- Other
|
- Other
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
@@ -90,7 +81,7 @@ body:
|
|||||||
label: What is the version of Gluetun
|
label: What is the version of Gluetun
|
||||||
description: |
|
description: |
|
||||||
Copy paste the version line at the top of your logs.
|
Copy paste the version line at the top of your logs.
|
||||||
It MUST be in the form `Running version latest built on 2020-03-13T01:30:06Z (commit d0f678c)`.
|
It should be in the form `Running version latest built on 2020-03-13T01:30:06Z (commit d0f678c)`.
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
@@ -103,9 +94,9 @@ body:
|
|||||||
- type: textarea
|
- type: textarea
|
||||||
id: logs
|
id: logs
|
||||||
attributes:
|
attributes:
|
||||||
label: Share your logs (at least 10 lines)
|
label: Share your logs
|
||||||
description: No sensitive information is logged out except when running with `LOG_LEVEL=debug`.
|
description: No sensitive information is logged out except when running with `LOG_LEVEL=debug`.
|
||||||
render: plain text
|
render: log
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
|
|||||||
3
.github/ISSUE_TEMPLATE/config.yml
vendored
3
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,7 +1,4 @@
|
|||||||
contact_links:
|
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?
|
- name: Configuration help?
|
||||||
url: https://github.com/qdm12/gluetun/discussions/new
|
url: https://github.com/qdm12/gluetun/discussions/new
|
||||||
about: Please create a Github discussion.
|
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,
|
If the list of servers requires to login **or** is hidden behind an interactive configurator,
|
||||||
you can only use a custom Openvpn configuration file.
|
you can only use a custom Openvpn configuration file.
|
||||||
[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.
|
[The Wiki](https://github.com/qdm12/gluetun/wiki/Openvpn-file) describes how to do so.
|
||||||
|
|||||||
18
.github/ISSUE_TEMPLATE/wiki issue.yml
vendored
Normal file
18
.github/ISSUE_TEMPLATE/wiki issue.yml
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
name: Wiki issue
|
||||||
|
description: Report a Wiki issue
|
||||||
|
title: "Wiki issue: "
|
||||||
|
labels: ["📄 Wiki issue"]
|
||||||
|
body:
|
||||||
|
- type: input
|
||||||
|
id: url
|
||||||
|
attributes:
|
||||||
|
label: "URL to the Wiki page"
|
||||||
|
placeholder: "https://github.com/qdm12/gluetun/wiki/OpenVPN-options"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: description
|
||||||
|
attributes:
|
||||||
|
label: "What's the issue?"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
51
.github/labels.yml
vendored
51
.github/labels.yml
vendored
@@ -1,13 +1,18 @@
|
|||||||
# Temporary status
|
- name: "Bug :bug:"
|
||||||
- name: "🗯️ Waiting for feedback"
|
color: "b60205"
|
||||||
color: "aadefa"
|
description: ""
|
||||||
|
- name: "Feature request :bulb:"
|
||||||
|
color: "0e8a16"
|
||||||
|
description: ""
|
||||||
|
- name: "Help wanted :pray:"
|
||||||
|
color: "4caf50"
|
||||||
|
description: ""
|
||||||
|
- name: "Documentation :memo:"
|
||||||
|
color: "c5def5"
|
||||||
|
description: ""
|
||||||
|
- name: "Needs more info :thinking:"
|
||||||
|
color: "795548"
|
||||||
description: ""
|
description: ""
|
||||||
- name: "🔴 Blocked"
|
|
||||||
color: "ff3f14"
|
|
||||||
description: "Blocked by another issue or pull request"
|
|
||||||
- name: "🔒 After next release"
|
|
||||||
color: "e8f274"
|
|
||||||
description: "Will be done after the next release"
|
|
||||||
|
|
||||||
# Priority
|
# Priority
|
||||||
- name: "🚨 Urgent"
|
- name: "🚨 Urgent"
|
||||||
@@ -17,18 +22,7 @@
|
|||||||
color: "4285f4"
|
color: "4285f4"
|
||||||
description: ""
|
description: ""
|
||||||
|
|
||||||
# Complexity
|
|
||||||
- name: "☣️ Hard to do"
|
|
||||||
color: "7d0008"
|
|
||||||
description: ""
|
|
||||||
- name: "🟩 Easy to do"
|
|
||||||
color: "34cf43"
|
|
||||||
description: ""
|
|
||||||
|
|
||||||
# VPN providers
|
# VPN providers
|
||||||
- name: ":cloud: AirVPN"
|
|
||||||
color: "cfe8d4"
|
|
||||||
description: ""
|
|
||||||
- name: ":cloud: Cyberghost"
|
- name: ":cloud: Cyberghost"
|
||||||
color: "cfe8d4"
|
color: "cfe8d4"
|
||||||
description: ""
|
description: ""
|
||||||
@@ -70,17 +64,12 @@
|
|||||||
- name: ":cloud: PureVPN"
|
- name: ":cloud: PureVPN"
|
||||||
color: "cfe8d4"
|
color: "cfe8d4"
|
||||||
description: ""
|
description: ""
|
||||||
- name: ":cloud: SlickVPN"
|
|
||||||
color: "cfe8d4"
|
|
||||||
description: ""
|
|
||||||
- name: ":cloud: Surfshark"
|
- name: ":cloud: Surfshark"
|
||||||
color: "cfe8d4"
|
color: "cfe8d4"
|
||||||
description: ""
|
description: ""
|
||||||
- name: ":cloud: Torguard"
|
- name: ":cloud: Torguard"
|
||||||
color: "cfe8d4"
|
color: "cfe8d4"
|
||||||
description: ""
|
description: ""
|
||||||
- name: ":cloud: VPNSecure.me"
|
|
||||||
color: "cfe8d4"
|
|
||||||
- name: ":cloud: VPNUnlimited"
|
- name: ":cloud: VPNUnlimited"
|
||||||
color: "cfe8d4"
|
color: "cfe8d4"
|
||||||
description: ""
|
description: ""
|
||||||
@@ -95,9 +84,6 @@
|
|||||||
description: ""
|
description: ""
|
||||||
|
|
||||||
# Problem category
|
# Problem category
|
||||||
- name: "Config problem"
|
|
||||||
color: "ffc7ea"
|
|
||||||
description: ""
|
|
||||||
- name: "Openvpn"
|
- name: "Openvpn"
|
||||||
color: "ffc7ea"
|
color: "ffc7ea"
|
||||||
description: ""
|
description: ""
|
||||||
@@ -110,15 +96,6 @@
|
|||||||
- name: "Firewall"
|
- name: "Firewall"
|
||||||
color: "ffc7ea"
|
color: "ffc7ea"
|
||||||
description: ""
|
description: ""
|
||||||
- name: "Routing"
|
|
||||||
color: "ffc7ea"
|
|
||||||
description: ""
|
|
||||||
- name: "IPv6"
|
|
||||||
color: "ffc7ea"
|
|
||||||
description: ""
|
|
||||||
- name: "Port forwarding"
|
|
||||||
color: "ffc7ea"
|
|
||||||
description: ""
|
|
||||||
- name: "HTTP proxy"
|
- name: "HTTP proxy"
|
||||||
color: "ffc7ea"
|
color: "ffc7ea"
|
||||||
description: ""
|
description: ""
|
||||||
|
|||||||
35
.github/workflows/ci-skip.yml
vendored
35
.github/workflows/ci-skip.yml
vendored
@@ -1,35 +0,0 @@
|
|||||||
name: No trigger file paths
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
paths-ignore:
|
|
||||||
- .github/workflows/ci.yml
|
|
||||||
- cmd/**
|
|
||||||
- internal/**
|
|
||||||
- pkg/**
|
|
||||||
- .dockerignore
|
|
||||||
- .golangci.yml
|
|
||||||
- Dockerfile
|
|
||||||
- go.mod
|
|
||||||
- go.sum
|
|
||||||
pull_request:
|
|
||||||
paths-ignore:
|
|
||||||
- .github/workflows/ci.yml
|
|
||||||
- cmd/**
|
|
||||||
- internal/**
|
|
||||||
- pkg/**
|
|
||||||
- .dockerignore
|
|
||||||
- .golangci.yml
|
|
||||||
- Dockerfile
|
|
||||||
- go.mod
|
|
||||||
- go.sum
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
verify:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
actions: read
|
|
||||||
steps:
|
|
||||||
- name: No trigger path triggered for required verify workflow.
|
|
||||||
run: exit 0
|
|
||||||
80
.github/workflows/ci.yml
vendored
80
.github/workflows/ci.yml
vendored
@@ -17,6 +17,8 @@ on:
|
|||||||
- go.mod
|
- go.mod
|
||||||
- go.sum
|
- go.sum
|
||||||
pull_request:
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
paths:
|
paths:
|
||||||
- .github/workflows/ci.yml
|
- .github/workflows/ci.yml
|
||||||
- cmd/**
|
- cmd/**
|
||||||
@@ -30,28 +32,28 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
verify:
|
verify:
|
||||||
|
# Only run if it's a push event or if it's a PR from this repository, and it is not dependabot.
|
||||||
|
if: |
|
||||||
|
github.actor != 'dependabot[bot]' &&
|
||||||
|
(github.event_name == 'push' ||
|
||||||
|
github.event_name == 'release' ||
|
||||||
|
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository))
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
|
||||||
actions: read
|
|
||||||
contents: read
|
|
||||||
env:
|
env:
|
||||||
DOCKER_BUILDKIT: "1"
|
DOCKER_BUILDKIT: "1"
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- uses: reviewdog/action-misspell@v1
|
- uses: reviewdog/action-misspell@v1
|
||||||
with:
|
with:
|
||||||
locale: "US"
|
locale: "US"
|
||||||
level: error
|
level: error
|
||||||
exclude: |
|
|
||||||
./internal/storage/servers.json
|
|
||||||
*.md
|
|
||||||
|
|
||||||
- name: Linting
|
- name: Linting
|
||||||
run: docker build --target lint .
|
run: docker build --target lint .
|
||||||
|
|
||||||
- name: Mocks check
|
- name: Go mod tidy check
|
||||||
run: docker build --target mocks .
|
run: docker build --target tidy .
|
||||||
|
|
||||||
- name: Build test image
|
- name: Build test image
|
||||||
run: docker build --target test -t test-container .
|
run: docker build --target test -t test-container .
|
||||||
@@ -63,79 +65,65 @@ jobs:
|
|||||||
-v "$(pwd)/coverage.txt:/tmp/gobuild/coverage.txt" \
|
-v "$(pwd)/coverage.txt:/tmp/gobuild/coverage.txt" \
|
||||||
test-container
|
test-container
|
||||||
|
|
||||||
|
- name: Code security analysis
|
||||||
|
uses: snyk/actions/golang@master
|
||||||
|
env:
|
||||||
|
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
|
||||||
|
|
||||||
- name: Build final image
|
- name: Build final image
|
||||||
run: docker build -t final-image .
|
run: docker build -t final-image .
|
||||||
|
|
||||||
codeql:
|
# - name: Image security analysis
|
||||||
runs-on: ubuntu-latest
|
# uses: snyk/actions/docker@master
|
||||||
permissions:
|
# env:
|
||||||
actions: read
|
# SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
|
||||||
contents: read
|
# with:
|
||||||
security-events: write
|
# image: final-image
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: github/codeql-action/init@v3
|
|
||||||
with:
|
|
||||||
languages: go
|
|
||||||
- uses: github/codeql-action/autobuild@v3
|
|
||||||
- uses: github/codeql-action/analyze@v3
|
|
||||||
|
|
||||||
publish:
|
publish:
|
||||||
|
# Only run if it's a push event or if it's a PR from this repository
|
||||||
if: |
|
if: |
|
||||||
github.repository == 'qdm12/gluetun' &&
|
github.event_name == 'push' ||
|
||||||
(
|
github.event_name == 'release' ||
|
||||||
github.event_name == 'push' ||
|
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository)
|
||||||
github.event_name == 'release' ||
|
needs: [verify]
|
||||||
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && github.actor != 'dependabot[bot]')
|
|
||||||
)
|
|
||||||
needs: [verify, codeql]
|
|
||||||
permissions:
|
|
||||||
actions: read
|
|
||||||
contents: read
|
|
||||||
packages: write
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
# extract metadata (tags, labels) for Docker
|
# extract metadata (tags, labels) for Docker
|
||||||
# https://github.com/docker/metadata-action
|
# https://github.com/docker/metadata-action
|
||||||
- name: Extract Docker metadata
|
- name: Extract Docker metadata
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@v5
|
uses: docker/metadata-action@v3
|
||||||
with:
|
with:
|
||||||
flavor: |
|
flavor: |
|
||||||
latest=${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }}
|
latest=${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }}
|
||||||
images: |
|
images: |
|
||||||
ghcr.io/qdm12/gluetun
|
|
||||||
qmcgaw/gluetun
|
qmcgaw/gluetun
|
||||||
qmcgaw/private-internet-access
|
qmcgaw/private-internet-access
|
||||||
tags: |
|
tags: |
|
||||||
|
type=ref,event=branch,enable=${{ github.ref != format('refs/heads/{0}', github.event.repository.default_branch) }}
|
||||||
type=ref,event=pr
|
type=ref,event=pr
|
||||||
type=semver,pattern=v{{major}}.{{minor}}.{{patch}}
|
type=semver,pattern=v{{major}}.{{minor}}.{{patch}}
|
||||||
type=semver,pattern=v{{major}}.{{minor}}
|
type=semver,pattern=v{{major}}.{{minor}}
|
||||||
type=semver,pattern=v{{major}},enable=${{ !startsWith(github.ref, 'refs/tags/v0.') }}
|
type=semver,pattern=v{{major}},enable=${{ !startsWith(github.ref, 'refs/tags/v0.') }}
|
||||||
type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }}
|
type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }}
|
||||||
|
|
||||||
- uses: docker/setup-qemu-action@v3
|
- uses: docker/setup-qemu-action@v1
|
||||||
- uses: docker/setup-buildx-action@v3
|
- uses: docker/setup-buildx-action@v1
|
||||||
|
|
||||||
- uses: docker/login-action@v3
|
- uses: docker/login-action@v1
|
||||||
with:
|
with:
|
||||||
username: qmcgaw
|
username: qmcgaw
|
||||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||||
|
|
||||||
- uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
registry: ghcr.io
|
|
||||||
username: qdm12
|
|
||||||
password: ${{ github.token }}
|
|
||||||
|
|
||||||
- name: Short commit
|
- name: Short commit
|
||||||
id: shortcommit
|
id: shortcommit
|
||||||
run: echo "::set-output name=value::$(git rev-parse --short HEAD)"
|
run: echo "::set-output name=value::$(git rev-parse --short HEAD)"
|
||||||
|
|
||||||
- name: Build and push final image
|
- name: Build and push final image
|
||||||
uses: docker/build-push-action@v5
|
uses: docker/build-push-action@v2.9.0
|
||||||
with:
|
with:
|
||||||
platforms: linux/amd64,linux/386,linux/arm64,linux/arm/v6,linux/arm/v7,linux/ppc64le
|
platforms: linux/amd64,linux/386,linux/arm64,linux/arm/v6,linux/arm/v7,linux/ppc64le
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
|||||||
21
.github/workflows/closed-issue.yml
vendored
21
.github/workflows/closed-issue.yml
vendored
@@ -1,21 +0,0 @@
|
|||||||
name: Closed issue
|
|
||||||
on:
|
|
||||||
issues:
|
|
||||||
types: [closed]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
comment:
|
|
||||||
permissions:
|
|
||||||
issues: write
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: peter-evans/create-or-update-comment@v4
|
|
||||||
with:
|
|
||||||
token: ${{ github.token }}
|
|
||||||
issue-number: ${{ github.event.issue.number }}
|
|
||||||
body: |
|
|
||||||
Closed issues are **NOT** monitored, so commenting here is likely to be not seen.
|
|
||||||
If you think this is *still unresolved* and have **more information** to bring, please create another issue.
|
|
||||||
|
|
||||||
This is an automated comment setup because @qdm12 is the sole maintainer of this project
|
|
||||||
which became too popular to monitor issues closed.
|
|
||||||
37
.github/workflows/dependabot.yml
vendored
Normal file
37
.github/workflows/dependabot.yml
vendored
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
name: Dependabot
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
paths:
|
||||||
|
- .github/workflows/dependabot.yml
|
||||||
|
- cmd/**
|
||||||
|
- internal/**
|
||||||
|
- pkg/**
|
||||||
|
- .dockerignore
|
||||||
|
- .golangci.yml
|
||||||
|
- Dockerfile
|
||||||
|
- go.mod
|
||||||
|
- go.sum
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
verify:
|
||||||
|
if: ${{ github.actor == 'dependabot[bot]' }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
DOCKER_BUILDKIT: "1"
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2.4.0
|
||||||
|
|
||||||
|
- name: Build test image
|
||||||
|
run: docker build --target test -t test-container .
|
||||||
|
|
||||||
|
- name: Run tests in test container
|
||||||
|
run: |
|
||||||
|
touch coverage.txt
|
||||||
|
docker run --rm \
|
||||||
|
-v "$(pwd)/coverage.txt:/tmp/gobuild/coverage.txt" \
|
||||||
|
test-container
|
||||||
|
|
||||||
|
- name: Build final image
|
||||||
|
run: docker build -t final-image .
|
||||||
21
.github/workflows/dockerhub-description.yml
vendored
Normal file
21
.github/workflows/dockerhub-description.yml
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
name: Docker Hub description
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [master]
|
||||||
|
paths:
|
||||||
|
- README.md
|
||||||
|
- .github/workflows/dockerhub-description.yml
|
||||||
|
jobs:
|
||||||
|
dockerHubDescription:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2.4.0
|
||||||
|
- name: Docker Hub Description
|
||||||
|
uses: peter-evans/dockerhub-description@v2
|
||||||
|
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
|
||||||
40
.github/workflows/fork.yml
vendored
Normal file
40
.github/workflows/fork.yml
vendored
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
name: Fork
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
paths:
|
||||||
|
- .github/workflows/fork.yml
|
||||||
|
- cmd/**
|
||||||
|
- internal/**
|
||||||
|
- pkg/**
|
||||||
|
- .dockerignore
|
||||||
|
- .golangci.yml
|
||||||
|
- Dockerfile
|
||||||
|
- go.mod
|
||||||
|
- go.sum
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
verify:
|
||||||
|
if: github.event.pull_request.head.repo.full_name != github.repository && github.actor != 'dependabot[bot]'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
DOCKER_BUILDKIT: "1"
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2.4.0
|
||||||
|
|
||||||
|
- name: Linting
|
||||||
|
run: docker build --target lint .
|
||||||
|
|
||||||
|
- name: Build test image
|
||||||
|
run: docker build --target test -t test-container .
|
||||||
|
|
||||||
|
- name: Run tests in test container
|
||||||
|
run: |
|
||||||
|
touch coverage.txt
|
||||||
|
docker run --rm \
|
||||||
|
-v "$(pwd)/coverage.txt:/tmp/gobuild/coverage.txt" \
|
||||||
|
test-container
|
||||||
|
|
||||||
|
- name: Build final image
|
||||||
|
run: docker build -t final-image .
|
||||||
6
.github/workflows/labels.yml
vendored
6
.github/workflows/labels.yml
vendored
@@ -7,11 +7,9 @@ on:
|
|||||||
- .github/workflows/labels.yml
|
- .github/workflows/labels.yml
|
||||||
jobs:
|
jobs:
|
||||||
labeler:
|
labeler:
|
||||||
permissions:
|
|
||||||
issues: write
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v2.4.0
|
||||||
- uses: crazy-max/ghaction-github-labeler@v5
|
- uses: crazy-max/ghaction-github-labeler@v3
|
||||||
with:
|
with:
|
||||||
yaml-file: .github/labels.yml
|
yaml-file: .github/labels.yml
|
||||||
|
|||||||
21
.github/workflows/markdown-skip.yml
vendored
21
.github/workflows/markdown-skip.yml
vendored
@@ -1,21 +0,0 @@
|
|||||||
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
46
.github/workflows/markdown.yml
vendored
@@ -1,46 +0,0 @@
|
|||||||
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@v4
|
|
||||||
|
|
||||||
- uses: DavidAnson/markdownlint-cli2-action@v14
|
|
||||||
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
|
|
||||||
15
.github/workflows/misspell.yml
vendored
Normal file
15
.github/workflows/misspell.yml
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
name: Misspells
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches: [master]
|
||||||
|
push:
|
||||||
|
branches: [master]
|
||||||
|
jobs:
|
||||||
|
misspell:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2.4.0
|
||||||
|
- uses: reviewdog/action-misspell@v1
|
||||||
|
with:
|
||||||
|
locale: "US"
|
||||||
|
level: error
|
||||||
22
.github/workflows/opened-issue.yml
vendored
22
.github/workflows/opened-issue.yml
vendored
@@ -1,22 +0,0 @@
|
|||||||
name: Opened issue
|
|
||||||
on:
|
|
||||||
issues:
|
|
||||||
types: [opened]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
comment:
|
|
||||||
permissions:
|
|
||||||
issues: write
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: peter-evans/create-or-update-comment@v4
|
|
||||||
with:
|
|
||||||
token: ${{ github.token }}
|
|
||||||
issue-number: ${{ github.event.issue.number }}
|
|
||||||
body: |
|
|
||||||
@qdm12 is more or less the only maintainer of this project and works on it in his free time.
|
|
||||||
Please:
|
|
||||||
- **do not** ask for updates, be patient
|
|
||||||
- :+1: the issue to show your support instead of commenting
|
|
||||||
@qdm12 usually checks issues at least once a week, if this is a new urgent bug,
|
|
||||||
[revert to an older tagged container image](https://github.com/qdm12/gluetun-wiki/blob/main/setup/docker-image-tags.md)
|
|
||||||
@@ -7,69 +7,52 @@ issues:
|
|||||||
- path: _test\.go
|
- path: _test\.go
|
||||||
linters:
|
linters:
|
||||||
- dupl
|
- dupl
|
||||||
|
- maligned
|
||||||
- goerr113
|
- goerr113
|
||||||
- containedctx
|
- path: internal/server/
|
||||||
- goconst
|
|
||||||
- maintidx
|
|
||||||
- path: "internal\\/server\\/.+\\.go"
|
|
||||||
linters:
|
linters:
|
||||||
- dupl
|
- dupl
|
||||||
- path: "internal\\/configuration\\/settings\\/.+\\.go"
|
- path: internal/configuration/
|
||||||
linters:
|
linters:
|
||||||
- dupl
|
- dupl
|
||||||
- text: "^mnd: Magic number: 0[0-9]{3}, in <argument> detected$"
|
- path: internal/constants/
|
||||||
source: "^.+= os\\.OpenFile\\(.+, .+, 0[0-9]{3}\\)"
|
linters:
|
||||||
|
- dupl
|
||||||
|
- text: "exported: exported var Err*"
|
||||||
|
linters:
|
||||||
|
- revive
|
||||||
|
- text: "mnd: Magic number: 0644*"
|
||||||
linters:
|
linters:
|
||||||
- gomnd
|
- gomnd
|
||||||
- text: "^mnd: Magic number: 0[0-9]{3}, in <argument> detected$"
|
- text: "mnd: Magic number: 0400*"
|
||||||
source: "^.+= os\\.MkdirAll\\(.+, 0[0-9]{3}\\)"
|
|
||||||
linters:
|
linters:
|
||||||
- gomnd
|
- gomnd
|
||||||
- linters:
|
- text: "variable 'mssFix' is only used in the if-statement*"
|
||||||
- lll
|
path: "openvpnconf.go"
|
||||||
source: "^//go:generate .+$"
|
|
||||||
- text: "returns interface \\(github\\.com\\/vishvananda\\/netlink\\.Link\\)"
|
|
||||||
linters:
|
linters:
|
||||||
- ireturn
|
- ifshort
|
||||||
- path: "internal\\/openvpn\\/pkcs8\\/descbc\\.go"
|
- text: "variable 'auth' is only used in the if-statement*"
|
||||||
text: "newCipherDESCBCBlock returns interface \\(github\\.com\\/youmark\\/pkcs8\\.Cipher\\)"
|
path: "openvpnconf.go"
|
||||||
linters:
|
linters:
|
||||||
- ireturn
|
- ifshort
|
||||||
- path: "internal\\/firewall\\/.*\\.go"
|
|
||||||
text: "string `-i ` has [1-9][0-9]* occurrences, make it a constant"
|
|
||||||
linters:
|
|
||||||
- goconst
|
|
||||||
- path: "internal\\/provider\\/ipvanish\\/updater\\/servers.go"
|
|
||||||
text: "string ` in ` has 3 occurrences, make it a constant"
|
|
||||||
linters:
|
|
||||||
- goconst
|
|
||||||
- path: "internal\\/vpn\\/portforward.go"
|
|
||||||
text: 'directive `//nolint:ireturn` is unused for linter "ireturn"'
|
|
||||||
linters:
|
|
||||||
- nolintlint
|
|
||||||
|
|
||||||
linters:
|
linters:
|
||||||
enable:
|
enable:
|
||||||
# - cyclop
|
# - cyclop
|
||||||
# - errorlint
|
# - errorlint
|
||||||
- asasalint
|
# - ireturn
|
||||||
|
# - varnamelen
|
||||||
|
# - wrapcheck
|
||||||
- asciicheck
|
- asciicheck
|
||||||
- bidichk
|
- bidichk
|
||||||
- bodyclose
|
- bodyclose
|
||||||
- containedctx
|
|
||||||
- decorder
|
|
||||||
- dogsled
|
- dogsled
|
||||||
- dupl
|
- dupl
|
||||||
- dupword
|
|
||||||
- durationcheck
|
- durationcheck
|
||||||
- errchkjson
|
|
||||||
- errname
|
- errname
|
||||||
- execinquery
|
|
||||||
- exhaustive
|
- exhaustive
|
||||||
- exportloopref
|
- exportloopref
|
||||||
- forcetypeassert
|
- forcetypeassert
|
||||||
- gci
|
- gci
|
||||||
- gocheckcompilerdirectives
|
|
||||||
- gochecknoglobals
|
- gochecknoglobals
|
||||||
- gochecknoinits
|
- gochecknoinits
|
||||||
- gocognit
|
- gocognit
|
||||||
@@ -84,42 +67,31 @@ linters:
|
|||||||
- gomoddirectives
|
- gomoddirectives
|
||||||
- goprintffuncname
|
- goprintffuncname
|
||||||
- gosec
|
- gosec
|
||||||
- gosmopolitan
|
- ifshort
|
||||||
- grouper
|
|
||||||
- importas
|
- importas
|
||||||
- interfacebloat
|
|
||||||
- ireturn
|
|
||||||
- lll
|
- lll
|
||||||
- maintidx
|
|
||||||
- makezero
|
- makezero
|
||||||
- mirror
|
|
||||||
- misspell
|
- misspell
|
||||||
- musttag
|
|
||||||
- nakedret
|
- nakedret
|
||||||
- nestif
|
- nestif
|
||||||
- nilerr
|
- nilerr
|
||||||
- nilnil
|
- nilnil
|
||||||
- noctx
|
- noctx
|
||||||
- nolintlint
|
- nolintlint
|
||||||
- nosprintfhostport
|
|
||||||
- paralleltest
|
|
||||||
- prealloc
|
- prealloc
|
||||||
- predeclared
|
- predeclared
|
||||||
|
- predeclared
|
||||||
- promlinter
|
- promlinter
|
||||||
- reassign
|
|
||||||
- revive
|
- revive
|
||||||
- rowserrcheck
|
- rowserrcheck
|
||||||
- sqlclosecheck
|
- sqlclosecheck
|
||||||
- tagalign
|
|
||||||
- tenv
|
- tenv
|
||||||
- thelper
|
- thelper
|
||||||
- tparallel
|
- tparallel
|
||||||
- unconvert
|
- unconvert
|
||||||
- unparam
|
- unparam
|
||||||
- usestdlibvars
|
|
||||||
- wastedassign
|
- wastedassign
|
||||||
- whitespace
|
- whitespace
|
||||||
- zerologlint
|
|
||||||
|
|
||||||
run:
|
run:
|
||||||
skip-dirs:
|
skip-dirs:
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"MD013": false
|
|
||||||
}
|
|
||||||
8
.vscode/extensions.json
vendored
8
.vscode/extensions.json
vendored
@@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
// This list should be kept to the strict minimum
|
|
||||||
// to develop this project.
|
|
||||||
"recommendations": [
|
|
||||||
"golang.go",
|
|
||||||
"davidanson.vscode-markdownlint",
|
|
||||||
],
|
|
||||||
}
|
|
||||||
35
.vscode/launch.json
vendored
35
.vscode/launch.json
vendored
@@ -1,35 +0,0 @@
|
|||||||
{
|
|
||||||
"version": "0.2.0",
|
|
||||||
"configurations": [
|
|
||||||
{
|
|
||||||
"name": "Update a VPN provider servers data",
|
|
||||||
"type": "go",
|
|
||||||
"request": "launch",
|
|
||||||
"cwd": "${workspaceFolder}",
|
|
||||||
"program": "cmd/gluetun/main.go",
|
|
||||||
"args": [
|
|
||||||
"update",
|
|
||||||
"${input:updateMode}",
|
|
||||||
"-providers",
|
|
||||||
"${input:provider}"
|
|
||||||
],
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"id": "provider",
|
|
||||||
"type": "promptString",
|
|
||||||
"description": "Please enter a provider (or comma separated list of providers)",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "updateMode",
|
|
||||||
"type": "pickString",
|
|
||||||
"description": "Update mode to use",
|
|
||||||
"options": [
|
|
||||||
"-maintainer",
|
|
||||||
"-enduser"
|
|
||||||
],
|
|
||||||
"default": "-maintainer"
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
29
.vscode/settings.json
vendored
29
.vscode/settings.json
vendored
@@ -1,29 +0,0 @@
|
|||||||
{
|
|
||||||
// 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"
|
|
||||||
}
|
|
||||||
113
Dockerfile
113
Dockerfile
@@ -1,22 +1,18 @@
|
|||||||
ARG ALPINE_VERSION=3.18
|
ARG ALPINE_VERSION=3.15
|
||||||
ARG GO_ALPINE_VERSION=3.18
|
ARG GO_ALPINE_VERSION=3.15
|
||||||
ARG GO_VERSION=1.21
|
ARG GO_VERSION=1.17
|
||||||
ARG XCPUTRANSLATE_VERSION=v0.6.0
|
ARG XCPUTRANSLATE_VERSION=v0.6.0
|
||||||
ARG GOLANGCI_LINT_VERSION=v1.56.2
|
ARG GOLANGCI_LINT_VERSION=v1.43.0
|
||||||
ARG MOCKGEN_VERSION=v1.6.0
|
|
||||||
ARG BUILDPLATFORM=linux/amd64
|
ARG BUILDPLATFORM=linux/amd64
|
||||||
|
|
||||||
FROM --platform=${BUILDPLATFORM} qmcgaw/xcputranslate:${XCPUTRANSLATE_VERSION} AS xcputranslate
|
FROM --platform=${BUILDPLATFORM} qmcgaw/xcputranslate:${XCPUTRANSLATE_VERSION} AS xcputranslate
|
||||||
FROM --platform=${BUILDPLATFORM} qmcgaw/binpot:golangci-lint-${GOLANGCI_LINT_VERSION} AS golangci-lint
|
FROM --platform=${BUILDPLATFORM} qmcgaw/binpot:golangci-lint-${GOLANGCI_LINT_VERSION} AS golangci-lint
|
||||||
FROM --platform=${BUILDPLATFORM} qmcgaw/binpot:mockgen-${MOCKGEN_VERSION} AS mockgen
|
|
||||||
|
|
||||||
FROM --platform=${BUILDPLATFORM} golang:${GO_VERSION}-alpine${GO_ALPINE_VERSION} AS base
|
FROM --platform=${BUILDPLATFORM} golang:${GO_VERSION}-alpine${GO_ALPINE_VERSION} AS base
|
||||||
COPY --from=xcputranslate /xcputranslate /usr/local/bin/xcputranslate
|
COPY --from=xcputranslate /xcputranslate /usr/local/bin/xcputranslate
|
||||||
# Note: findutils needed to have xargs support `-d` flag for mocks stage.
|
RUN apk --update add git g++
|
||||||
RUN apk --update add git g++ findutils
|
|
||||||
ENV CGO_ENABLED=0
|
ENV CGO_ENABLED=0
|
||||||
COPY --from=golangci-lint /bin /go/bin/golangci-lint
|
COPY --from=golangci-lint /bin /go/bin/golangci-lint
|
||||||
COPY --from=mockgen /bin /go/bin/mockgen
|
|
||||||
WORKDIR /tmp/gobuild
|
WORKDIR /tmp/gobuild
|
||||||
COPY go.mod go.sum ./
|
COPY go.mod go.sum ./
|
||||||
RUN go mod download
|
RUN go mod download
|
||||||
@@ -34,17 +30,14 @@ FROM --platform=${BUILDPLATFORM} base AS lint
|
|||||||
COPY .golangci.yml ./
|
COPY .golangci.yml ./
|
||||||
RUN golangci-lint run --timeout=10m
|
RUN golangci-lint run --timeout=10m
|
||||||
|
|
||||||
FROM --platform=${BUILDPLATFORM} base AS mocks
|
FROM --platform=${BUILDPLATFORM} base AS tidy
|
||||||
RUN git init && \
|
RUN git init && \
|
||||||
git config user.email ci@localhost && \
|
git config user.email ci@localhost && \
|
||||||
git config user.name ci && \
|
git config user.name ci && \
|
||||||
git config core.fileMode false && \
|
git add -A && git commit -m ci && \
|
||||||
git add -A && \
|
sed -i '/\/\/ indirect/d' go.mod && \
|
||||||
git commit -m "snapshot" && \
|
go mod tidy && \
|
||||||
grep -lr -E '^// Code generated by MockGen\. DO NOT EDIT\.$' . | xargs -r -d '\n' rm && \
|
git diff --exit-code -- go.mod
|
||||||
go generate -run "mockgen" ./... && \
|
|
||||||
git diff --exit-code && \
|
|
||||||
rm -rf .git/
|
|
||||||
|
|
||||||
FROM --platform=${BUILDPLATFORM} base AS build
|
FROM --platform=${BUILDPLATFORM} base AS build
|
||||||
ARG TARGETPLATFORM
|
ARG TARGETPLATFORM
|
||||||
@@ -73,12 +66,8 @@ LABEL \
|
|||||||
org.opencontainers.image.source="https://github.com/qdm12/gluetun" \
|
org.opencontainers.image.source="https://github.com/qdm12/gluetun" \
|
||||||
org.opencontainers.image.title="VPN swiss-knife like client for multiple VPN providers" \
|
org.opencontainers.image.title="VPN swiss-knife like client for multiple VPN providers" \
|
||||||
org.opencontainers.image.description="VPN swiss-knife like client to tunnel to multiple VPN servers using OpenVPN, IPtables, DNS over TLS, Shadowsocks, an HTTP proxy and Alpine Linux"
|
org.opencontainers.image.description="VPN swiss-knife like client to tunnel to multiple VPN servers using OpenVPN, IPtables, DNS over TLS, Shadowsocks, an HTTP proxy and Alpine Linux"
|
||||||
ENV VPN_SERVICE_PROVIDER=pia \
|
ENV VPNSP=pia \
|
||||||
VPN_TYPE=openvpn \
|
VPN_TYPE=openvpn \
|
||||||
# Common VPN options
|
|
||||||
VPN_ENDPOINT_IP= \
|
|
||||||
VPN_ENDPOINT_PORT= \
|
|
||||||
VPN_INTERFACE=tun0 \
|
|
||||||
# OpenVPN
|
# OpenVPN
|
||||||
OPENVPN_PROTOCOL=udp \
|
OPENVPN_PROTOCOL=udp \
|
||||||
OPENVPN_USER= \
|
OPENVPN_USER= \
|
||||||
@@ -88,59 +77,45 @@ ENV VPN_SERVICE_PROVIDER=pia \
|
|||||||
OPENVPN_VERSION=2.5 \
|
OPENVPN_VERSION=2.5 \
|
||||||
OPENVPN_VERBOSITY=1 \
|
OPENVPN_VERBOSITY=1 \
|
||||||
OPENVPN_FLAGS= \
|
OPENVPN_FLAGS= \
|
||||||
OPENVPN_CIPHERS= \
|
OPENVPN_CIPHER= \
|
||||||
OPENVPN_AUTH= \
|
OPENVPN_AUTH= \
|
||||||
OPENVPN_PROCESS_USER=root \
|
OPENVPN_ROOT=yes \
|
||||||
|
OPENVPN_TARGET_IP= \
|
||||||
|
OPENVPN_IPV6=off \
|
||||||
OPENVPN_CUSTOM_CONFIG= \
|
OPENVPN_CUSTOM_CONFIG= \
|
||||||
|
OPENVPN_INTERFACE=tun0 \
|
||||||
|
OPENVPN_PORT= \
|
||||||
# Wireguard
|
# Wireguard
|
||||||
WIREGUARD_CONF_SECRETFILE=/run/secrets/wg0.conf \
|
|
||||||
WIREGUARD_PRIVATE_KEY= \
|
WIREGUARD_PRIVATE_KEY= \
|
||||||
WIREGUARD_PRIVATE_KEY_SECRETFILE=/run/secrets/wireguard_private_key \
|
|
||||||
WIREGUARD_PRESHARED_KEY= \
|
WIREGUARD_PRESHARED_KEY= \
|
||||||
WIREGUARD_PRESHARED_KEY_SECRETFILE=/run/secrets/wireguard_preshared_key \
|
|
||||||
WIREGUARD_PUBLIC_KEY= \
|
WIREGUARD_PUBLIC_KEY= \
|
||||||
WIREGUARD_ALLOWED_IPS= \
|
WIREGUARD_ADDRESS= \
|
||||||
WIREGUARD_ADDRESSES= \
|
WIREGUARD_ENDPOINT_IP= \
|
||||||
WIREGUARD_ADDRESSES_SECRETFILE=/run/secrets/wireguard_addresses \
|
WIREGUARD_ENDPOINT_PORT= \
|
||||||
WIREGUARD_MTU=1400 \
|
WIREGUARD_INTERFACE=wg0 \
|
||||||
WIREGUARD_IMPLEMENTATION=auto \
|
|
||||||
# VPN server filtering
|
# VPN server filtering
|
||||||
SERVER_REGIONS= \
|
REGION= \
|
||||||
SERVER_COUNTRIES= \
|
COUNTRY= \
|
||||||
SERVER_CITIES= \
|
CITY= \
|
||||||
SERVER_HOSTNAMES= \
|
SERVER_HOSTNAME= \
|
||||||
SERVER_CATEGORIES= \
|
|
||||||
# # Mullvad only:
|
# # Mullvad only:
|
||||||
ISP= \
|
ISP= \
|
||||||
OWNED_ONLY=no \
|
OWNED=no \
|
||||||
# # Private Internet Access only:
|
# # Private Internet Access only:
|
||||||
PRIVATE_INTERNET_ACCESS_OPENVPN_ENCRYPTION_PRESET= \
|
PIA_ENCRYPTION= \
|
||||||
VPN_PORT_FORWARDING=off \
|
PORT_FORWARDING=off \
|
||||||
VPN_PORT_FORWARDING_LISTENING_PORT=0 \
|
PORT_FORWARDING_STATUS_FILE="/tmp/gluetun/forwarded_port" \
|
||||||
VPN_PORT_FORWARDING_PROVIDER= \
|
|
||||||
VPN_PORT_FORWARDING_STATUS_FILE="/tmp/gluetun/forwarded_port" \
|
|
||||||
# # Cyberghost only:
|
# # Cyberghost only:
|
||||||
OPENVPN_CERT= \
|
|
||||||
OPENVPN_KEY= \
|
|
||||||
OPENVPN_CLIENTCRT_SECRETFILE=/run/secrets/openvpn_clientcrt \
|
OPENVPN_CLIENTCRT_SECRETFILE=/run/secrets/openvpn_clientcrt \
|
||||||
OPENVPN_CLIENTKEY_SECRETFILE=/run/secrets/openvpn_clientkey \
|
OPENVPN_CLIENTKEY_SECRETFILE=/run/secrets/openvpn_clientkey \
|
||||||
# # VPNSecure only:
|
|
||||||
OPENVPN_ENCRYPTED_KEY= \
|
|
||||||
OPENVPN_ENCRYPTED_KEY_SECRETFILE=/run/secrets/openvpn_encrypted_key \
|
|
||||||
OPENVPN_KEY_PASSPHRASE= \
|
|
||||||
OPENVPN_KEY_PASSPHRASE_SECRETFILE=/run/secrets/openvpn_key_passphrase \
|
|
||||||
# # Nordvpn only:
|
# # Nordvpn only:
|
||||||
SERVER_NUMBER= \
|
SERVER_NUMBER= \
|
||||||
# # PIA only:
|
# # PIA and ProtonVPN only:
|
||||||
SERVER_NAMES= \
|
SERVER_NAME= \
|
||||||
# # ProtonVPN only:
|
# # ProtonVPN only:
|
||||||
FREE_ONLY= \
|
FREE_ONLY= \
|
||||||
# # Surfshark only:
|
# # Surfshark only:
|
||||||
MULTIHOP_ONLY= \
|
MULTIHOP_ONLY= \
|
||||||
# # VPN Secure only:
|
|
||||||
PREMIUM_ONLY= \
|
|
||||||
# # PIA only:
|
|
||||||
PORT_FORWARD_ONLY= \
|
|
||||||
# Firewall
|
# Firewall
|
||||||
FIREWALL=on \
|
FIREWALL=on \
|
||||||
FIREWALL_VPN_INPUT_PORTS= \
|
FIREWALL_VPN_INPUT_PORTS= \
|
||||||
@@ -151,8 +126,7 @@ ENV VPN_SERVICE_PROVIDER=pia \
|
|||||||
LOG_LEVEL=info \
|
LOG_LEVEL=info \
|
||||||
# Health
|
# Health
|
||||||
HEALTH_SERVER_ADDRESS=127.0.0.1:9999 \
|
HEALTH_SERVER_ADDRESS=127.0.0.1:9999 \
|
||||||
HEALTH_TARGET_ADDRESS=cloudflare.com:443 \
|
HEALTH_TARGET_ADDRESS=github.com:443 \
|
||||||
HEALTH_SUCCESS_WAIT_DURATION=5s \
|
|
||||||
HEALTH_VPN_DURATION_INITIAL=6s \
|
HEALTH_VPN_DURATION_INITIAL=6s \
|
||||||
HEALTH_VPN_DURATION_ADDITION=5s \
|
HEALTH_VPN_DURATION_ADDITION=5s \
|
||||||
# DNS over TLS
|
# DNS over TLS
|
||||||
@@ -169,13 +143,12 @@ ENV VPN_SERVICE_PROVIDER=pia \
|
|||||||
BLOCK_ADS=off \
|
BLOCK_ADS=off \
|
||||||
UNBLOCK= \
|
UNBLOCK= \
|
||||||
DNS_UPDATE_PERIOD=24h \
|
DNS_UPDATE_PERIOD=24h \
|
||||||
DNS_ADDRESS=127.0.0.1 \
|
DNS_PLAINTEXT_ADDRESS=127.0.0.1 \
|
||||||
DNS_KEEP_NAMESERVER=off \
|
DNS_KEEP_NAMESERVER=off \
|
||||||
# HTTP proxy
|
# HTTP proxy
|
||||||
HTTPPROXY= \
|
HTTPPROXY= \
|
||||||
HTTPPROXY_LOG=off \
|
HTTPPROXY_LOG=off \
|
||||||
HTTPPROXY_LISTENING_ADDRESS=":8888" \
|
HTTPPROXY_LISTENING_ADDRESS=":8888" \
|
||||||
HTTPPROXY_STEALTH=off \
|
|
||||||
HTTPPROXY_USER= \
|
HTTPPROXY_USER= \
|
||||||
HTTPPROXY_PASSWORD= \
|
HTTPPROXY_PASSWORD= \
|
||||||
HTTPPROXY_USER_SECRETFILE=/run/secrets/httpproxy_user \
|
HTTPPROXY_USER_SECRETFILE=/run/secrets/httpproxy_user \
|
||||||
@@ -187,23 +160,11 @@ ENV VPN_SERVICE_PROVIDER=pia \
|
|||||||
SHADOWSOCKS_PASSWORD= \
|
SHADOWSOCKS_PASSWORD= \
|
||||||
SHADOWSOCKS_PASSWORD_SECRETFILE=/run/secrets/shadowsocks_password \
|
SHADOWSOCKS_PASSWORD_SECRETFILE=/run/secrets/shadowsocks_password \
|
||||||
SHADOWSOCKS_CIPHER=chacha20-ietf-poly1305 \
|
SHADOWSOCKS_CIPHER=chacha20-ietf-poly1305 \
|
||||||
# Control server
|
|
||||||
HTTP_CONTROL_SERVER_LOG=on \
|
|
||||||
HTTP_CONTROL_SERVER_ADDRESS=":8000" \
|
|
||||||
# Server data updater
|
# Server data updater
|
||||||
UPDATER_PERIOD=0 \
|
UPDATER_PERIOD=0 \
|
||||||
UPDATER_MIN_RATIO=0.8 \
|
|
||||||
UPDATER_VPN_SERVICE_PROVIDERS= \
|
|
||||||
# Public IP
|
# Public IP
|
||||||
PUBLICIP_FILE="/tmp/gluetun/ip" \
|
PUBLICIP_FILE="/tmp/gluetun/ip" \
|
||||||
PUBLICIP_PERIOD=12h \
|
PUBLICIP_PERIOD=12h \
|
||||||
PUBLICIP_API=ipinfo \
|
|
||||||
PUBLICIP_API_TOKEN= \
|
|
||||||
# Pprof
|
|
||||||
PPROF_ENABLED=no \
|
|
||||||
PPROF_BLOCK_PROFILE_RATE=0 \
|
|
||||||
PPROF_MUTEX_PROFILE_RATE=0 \
|
|
||||||
PPROF_HTTP_SERVER_ADDRESS=":6060" \
|
|
||||||
# Extras
|
# Extras
|
||||||
VERSION_INFORMATION=on \
|
VERSION_INFORMATION=on \
|
||||||
TZ= \
|
TZ= \
|
||||||
@@ -213,12 +174,12 @@ ENTRYPOINT ["/gluetun-entrypoint"]
|
|||||||
EXPOSE 8000/tcp 8888/tcp 8388/tcp 8388/udp
|
EXPOSE 8000/tcp 8888/tcp 8388/tcp 8388/udp
|
||||||
HEALTHCHECK --interval=5s --timeout=5s --start-period=10s --retries=1 CMD /gluetun-entrypoint healthcheck
|
HEALTHCHECK --interval=5s --timeout=5s --start-period=10s --retries=1 CMD /gluetun-entrypoint healthcheck
|
||||||
ARG TARGETPLATFORM
|
ARG TARGETPLATFORM
|
||||||
RUN apk add --no-cache --update -l wget && \
|
RUN apk add --no-cache --update -l apk-tools && \
|
||||||
apk add --no-cache --update -X "https://dl-cdn.alpinelinux.org/alpine/v3.17/main" openvpn\~2.5 && \
|
apk add --no-cache --update -X "https://dl-cdn.alpinelinux.org/alpine/v3.12/main" openvpn==2.4.12-r0 && \
|
||||||
mv /usr/sbin/openvpn /usr/sbin/openvpn2.5 && \
|
mv /usr/sbin/openvpn /usr/sbin/openvpn2.4 && \
|
||||||
apk del openvpn && \
|
apk del openvpn && \
|
||||||
apk add --no-cache --update openvpn ca-certificates iptables ip6tables unbound tzdata && \
|
apk add --no-cache --update openvpn ca-certificates iptables ip6tables unbound tzdata && \
|
||||||
mv /usr/sbin/openvpn /usr/sbin/openvpn2.6 && \
|
mv /usr/sbin/openvpn /usr/sbin/openvpn2.5 && \
|
||||||
# Fix vulnerability issue
|
# Fix vulnerability issue
|
||||||
apk add --no-cache --update busybox && \
|
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 && \
|
rm -rf /var/cache/apk/* /etc/unbound/* /usr/sbin/unbound-* /etc/openvpn/*.sh /usr/lib/openvpn/plugins/openvpn-plugin-down-root.so && \
|
||||||
|
|||||||
257
README.md
257
README.md
@@ -1,130 +1,127 @@
|
|||||||
# Gluetun VPN client
|
# Gluetun VPN client
|
||||||
|
|
||||||
Lightweight swiss-knife-like VPN client to multiple VPN service providers
|
*Lightweight swiss-knife-like VPN client to tunnel to Cyberghost, ExpressVPN, FastestVPN,
|
||||||
|
HideMyAss, IPVanish, IVPN, Mullvad, NordVPN, Perfect Privacy, Privado, Private Internet Access, PrivateVPN,
|
||||||

|
ProtonVPN, PureVPN, Surfshark, TorGuard, VPNUnlimited, VyprVPN, WeVPN and Windscribe VPN servers
|
||||||
|
using Go, OpenVPN or Wireguard, iptables, DNS over TLS, ShadowSocks and an HTTP proxy*
|
||||||
[](https://github.com/qdm12/gluetun/actions/workflows/ci.yml)
|
|
||||||
|
**ANNOUNCEMENT**: Large settings refactor merged on 2022-06-01, please file issues if you find any problem!
|
||||||
[](https://hub.docker.com/r/qmcgaw/gluetun)
|
|
||||||
[](https://hub.docker.com/r/qmcgaw/gluetun)
|

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

|
[](https://hub.docker.com/r/qmcgaw/gluetun)
|
||||||

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

|
[](https://hub.docker.com/r/qmcgaw/gluetun)
|
||||||

|
|
||||||
|

|
||||||
[](https://hub.docker.com/r/qmcgaw/gluetun/tags)
|

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

|
||||||
[](https://github.com/qdm12/gluetun/graphs/contributors)
|

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

|
[](https://github.com/qdm12/gluetun/pulls?q=is%3Apr+is%3Aclosed)
|
||||||

|
[](https://github.com/qdm12/gluetun/issues)
|
||||||

|
[](https://github.com/qdm12/gluetun/issues?q=is%3Aissue+is%3Aclosed)
|
||||||
|
|
||||||

|
[](https://github.com/qdm12/gluetun)
|
||||||
|

|
||||||
## Quick links
|

|
||||||
|

|
||||||
- [Setup](#setup)
|
|
||||||
- [Features](#features)
|

|
||||||
- Problem?
|
|
||||||
- Check the Wiki [common errors](https://github.com/qdm12/gluetun-wiki/tree/main/errors) and [faq](https://github.com/qdm12/gluetun-wiki/tree/main/faq)
|
## Quick links
|
||||||
- [Start a discussion](https://github.com/qdm12/gluetun/discussions)
|
|
||||||
- [Fix the Unraid template](https://github.com/qdm12/gluetun/discussions/550)
|
- [Setup](#Setup)
|
||||||
- Suggestion?
|
- [Features](#Features)
|
||||||
- [Create an issue](https://github.com/qdm12/gluetun/issues)
|
- Problem?
|
||||||
- Happy?
|
- [Check the Wiki](https://github.com/qdm12/gluetun/wiki)
|
||||||
- Sponsor me on [github.com/sponsors/qdm12](https://github.com/sponsors/qdm12)
|
- [Start a discussion](https://github.com/qdm12/gluetun/discussions)
|
||||||
- Donate to [paypal.me/qmcgaw](https://www.paypal.me/qmcgaw)
|
- [Fix the Unraid template](https://github.com/qdm12/gluetun/discussions/550)
|
||||||
- Drop me [an email](mailto:quentin.mcgaw@gmail.com)
|
- Suggestion?
|
||||||
- **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)
|
- [Create an issue](https://github.com/qdm12/gluetun/issues)
|
||||||
- Video:
|
- [Join the Slack channel](https://join.slack.com/t/qdm12/shared_invite/enQtOTE0NjcxNTM1ODc5LTYyZmVlOTM3MGI4ZWU0YmJkMjUxNmQ4ODQ2OTAwYzMxMTlhY2Q1MWQyOWUyNjc2ODliNjFjMDUxNWNmNzk5MDk)
|
||||||
|
- Happy?
|
||||||
[](https://youtu.be/0F6I03LQcI4)
|
- Sponsor me on [github.com/sponsors/qdm12](https://github.com/sponsors/qdm12)
|
||||||
|
- Donate to [paypal.me/qmcgaw](https://www.paypal.me/qmcgaw)
|
||||||
- [Substack Console interview](https://console.substack.com/p/console-72)
|
- Drop me [an email](mailto:quentin.mcgaw@gmail.com)
|
||||||
|
- Video:
|
||||||
## Features
|
|
||||||
|
[](https://youtu.be/0F6I03LQcI4)
|
||||||
- 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
|
- [Substack Console interview](https://console.substack.com/p/console-72)
|
||||||
- Supports OpenVPN for all providers listed
|
|
||||||
- Supports Wireguard both kernelspace and userspace
|
## Features
|
||||||
- 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)
|
- Based on Alpine 3.15 for a small Docker image of 29MB
|
||||||
- For custom Wireguard configurations using [the custom provider](https://github.com/qdm12/gluetun-wiki/blob/main/setup/providers/custom.md)
|
- Supports: **Cyberghost**, **ExpressVPN**, **FastestVPN**, **HideMyAss**, **IPVanish**, **IVPN**, **Mullvad**, **NordVPN**, **Perfect Privacy**, **Privado**, **Private Internet Access**, **PrivateVPN**, **ProtonVPN**, **PureVPN**, **Surfshark**, **TorGuard**, **VPNUnlimited**, **Vyprvpn**, **WeVPN**, **Windscribe** servers
|
||||||
- More in progress, see [#134](https://github.com/qdm12/gluetun/issues/134)
|
- Supports OpenVPN for all providers listed
|
||||||
- DNS over TLS baked in with service provider(s) of your choice
|
- Supports Wireguard both kernelspace and userspace
|
||||||
- DNS fine blocking of malicious/ads/surveillance hostnames and IP addresses, with live update every 24 hours
|
- For **Mullvad**, **Ivpn** and **Windscribe**
|
||||||
- Choose the vpn network protocol, `udp` or `tcp`
|
- For **Torguard**, **VPN Unlimited** and **WeVPN** using [the custom provider](https://github.com/qdm12/gluetun/wiki/Custom-provider)
|
||||||
- Built in firewall kill switch to allow traffic only with needed the VPN servers and LAN devices
|
- For custom Wireguard configurations using [the custom provider](https://github.com/qdm12/gluetun/wiki/Custom-provider)
|
||||||
- Built in Shadowsocks proxy (protocol based on SOCKS5 with an encryption layer, tunnels TCP+UDP)
|
- More in progress, see [#134](https://github.com/qdm12/gluetun/issues/134)
|
||||||
- Built in HTTP proxy (tunnels HTTP and HTTPS through TCP)
|
- DNS over TLS baked in with service provider(s) of your choice
|
||||||
- [Connect other containers to it](https://github.com/qdm12/gluetun-wiki/blob/main/setup/connect-a-container-to-gluetun.md)
|
- DNS fine blocking of malicious/ads/surveillance hostnames and IP addresses, with live update every 24 hours
|
||||||
- [Connect LAN devices to it](https://github.com/qdm12/gluetun-wiki/blob/main/setup/connect-a-lan-device-to-gluetun.md)
|
- Choose the vpn network protocol, `udp` or `tcp`
|
||||||
- Compatible with amd64, i686 (32 bit), **ARM** 64 bit, ARM 32 bit v6 and v7, and even ppc64le 🎆
|
- Built in firewall kill switch to allow traffic only with needed the VPN servers and LAN devices
|
||||||
- [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)
|
- Built in Shadowsocks proxy (protocol based on SOCKS5 with an encryption layer, tunnels TCP+UDP)
|
||||||
- Possibility of split horizon DNS by selecting multiple DNS over TLS providers
|
- Built in HTTP proxy (tunnels HTTP and HTTPS through TCP)
|
||||||
- Unbound subprogram drops root privileges once launched
|
- [Connect other containers to it](https://github.com/qdm12/gluetun/wiki/Connect-a-container-to-gluetun)
|
||||||
- Can work as a Kubernetes sidecar container, thanks @rorph
|
- [Connect LAN devices to it](https://github.com/qdm12/gluetun/wiki/Connect-a-LAN-device-to-gluetun)
|
||||||
|
- Compatible with amd64, i686 (32 bit), **ARM** 64 bit, ARM 32 bit v6 and v7, and even ppc64le 🎆
|
||||||
## Setup
|
- [Custom VPN server side port forwarding for Private Internet Access](https://github.com/qdm12/gluetun/wiki/Private-internet-access#vpn-server-port-forwarding)
|
||||||
|
- Possibility of split horizon DNS by selecting multiple DNS over TLS providers
|
||||||
🎉 There are now instructions specific to each VPN provider with examples to help you get started as quickly as possible!
|
- Unbound subprogram drops root privileges once launched
|
||||||
|
- Can work as a Kubernetes sidecar container, thanks @rorph
|
||||||
Go to the [Wiki](https://github.com/qdm12/gluetun-wiki)!
|
|
||||||
|
## Setup
|
||||||
[🐛 Found a bug in the Wiki?!](https://github.com/qdm12/gluetun-wiki/issues/new)
|
|
||||||
|
🎉 There are now instructions specific to each VPN provider with examples to help you get started as quickly as possible!
|
||||||
Here's a docker-compose.yml for the laziest:
|
|
||||||
|
Go to the [Wiki](https://github.com/qdm12/gluetun/wiki)!
|
||||||
```yml
|
|
||||||
version: "3"
|
[🐛 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+)
|
||||||
services:
|
|
||||||
gluetun:
|
Here's a docker-compose.yml for the laziest:
|
||||||
image: qmcgaw/gluetun
|
|
||||||
# container_name: gluetun
|
```yml
|
||||||
# line above must be uncommented to allow external containers to connect.
|
version: "3"
|
||||||
# See https://github.com/qdm12/gluetun-wiki/blob/main/setup/connect-a-container-to-gluetun.md#external-container-to-gluetun
|
services:
|
||||||
cap_add:
|
gluetun:
|
||||||
- NET_ADMIN
|
image: qmcgaw/gluetun
|
||||||
devices:
|
# container_name: gluetun
|
||||||
- /dev/net/tun:/dev/net/tun
|
# 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
|
||||||
ports:
|
cap_add:
|
||||||
- 8888:8888/tcp # HTTP proxy
|
- NET_ADMIN
|
||||||
- 8388:8388/tcp # Shadowsocks
|
ports:
|
||||||
- 8388:8388/udp # Shadowsocks
|
- 8888:8888/tcp # HTTP proxy
|
||||||
volumes:
|
- 8388:8388/tcp # Shadowsocks
|
||||||
- /yourpath:/gluetun
|
- 8388:8388/udp # Shadowsocks
|
||||||
environment:
|
volumes:
|
||||||
# See https://github.com/qdm12/gluetun-wiki/tree/main/setup#setup
|
- /yourpath:/gluetun
|
||||||
- VPN_SERVICE_PROVIDER=ivpn
|
environment:
|
||||||
- VPN_TYPE=openvpn
|
# See https://github.com/qdm12/gluetun/wiki
|
||||||
# OpenVPN:
|
- VPNSP=ivpn
|
||||||
- OPENVPN_USER=
|
- VPN_TYPE=openvpn
|
||||||
- OPENVPN_PASSWORD=
|
# OpenVPN:
|
||||||
# Wireguard:
|
- OPENVPN_USER=
|
||||||
# - WIREGUARD_PRIVATE_KEY=wOEI9rqqbDwnN8/Bpp22sVz48T71vJ4fYmFWujulwUU=
|
- OPENVPN_PASSWORD=
|
||||||
# - WIREGUARD_ADDRESSES=10.64.222.21/32
|
# Wireguard:
|
||||||
# Timezone for accurate log times
|
# - WIREGUARD_PRIVATE_KEY=wOEI9rqqbDwnN8/Bpp22sVz48T71vJ4fYmFWujulwUU=
|
||||||
- TZ=
|
# - WIREGUARD_ADDRESS=10.64.222.21/32
|
||||||
# Server list updater
|
# Timezone for accurate log times
|
||||||
# See https://github.com/qdm12/gluetun-wiki/blob/main/setup/servers.md#update-the-vpn-servers-list
|
- TZ=
|
||||||
- UPDATER_PERIOD=
|
```
|
||||||
```
|
|
||||||
|
## License
|
||||||
🆕 Image also available as `ghcr.io/qdm12/gluetun`
|
|
||||||
|
[](https://github.com/qdm12/gluetun/master/LICENSE)
|
||||||
## License
|
|
||||||
|
|
||||||
[](https://github.com/qdm12/gluetun/blob/master/LICENSE)
|
|
||||||
|
|||||||
@@ -16,10 +16,10 @@ import (
|
|||||||
"github.com/qdm12/dns/pkg/unbound"
|
"github.com/qdm12/dns/pkg/unbound"
|
||||||
"github.com/qdm12/gluetun/internal/alpine"
|
"github.com/qdm12/gluetun/internal/alpine"
|
||||||
"github.com/qdm12/gluetun/internal/cli"
|
"github.com/qdm12/gluetun/internal/cli"
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
"github.com/qdm12/gluetun/internal/configuration/sources"
|
||||||
"github.com/qdm12/gluetun/internal/configuration/sources/env"
|
"github.com/qdm12/gluetun/internal/configuration/sources/env"
|
||||||
"github.com/qdm12/gluetun/internal/configuration/sources/files"
|
"github.com/qdm12/gluetun/internal/configuration/sources/files"
|
||||||
mux "github.com/qdm12/gluetun/internal/configuration/sources/merge"
|
"github.com/qdm12/gluetun/internal/configuration/sources/mux"
|
||||||
"github.com/qdm12/gluetun/internal/configuration/sources/secrets"
|
"github.com/qdm12/gluetun/internal/configuration/sources/secrets"
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
"github.com/qdm12/gluetun/internal/dns"
|
"github.com/qdm12/gluetun/internal/dns"
|
||||||
@@ -29,28 +29,22 @@ import (
|
|||||||
"github.com/qdm12/gluetun/internal/models"
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
"github.com/qdm12/gluetun/internal/netlink"
|
"github.com/qdm12/gluetun/internal/netlink"
|
||||||
"github.com/qdm12/gluetun/internal/openvpn"
|
"github.com/qdm12/gluetun/internal/openvpn"
|
||||||
"github.com/qdm12/gluetun/internal/openvpn/extract"
|
|
||||||
"github.com/qdm12/gluetun/internal/portforward"
|
"github.com/qdm12/gluetun/internal/portforward"
|
||||||
"github.com/qdm12/gluetun/internal/pprof"
|
|
||||||
"github.com/qdm12/gluetun/internal/provider"
|
|
||||||
"github.com/qdm12/gluetun/internal/publicip"
|
"github.com/qdm12/gluetun/internal/publicip"
|
||||||
pubipapi "github.com/qdm12/gluetun/internal/publicip/api"
|
|
||||||
"github.com/qdm12/gluetun/internal/routing"
|
"github.com/qdm12/gluetun/internal/routing"
|
||||||
"github.com/qdm12/gluetun/internal/server"
|
"github.com/qdm12/gluetun/internal/server"
|
||||||
"github.com/qdm12/gluetun/internal/shadowsocks"
|
"github.com/qdm12/gluetun/internal/shadowsocks"
|
||||||
"github.com/qdm12/gluetun/internal/storage"
|
"github.com/qdm12/gluetun/internal/storage"
|
||||||
"github.com/qdm12/gluetun/internal/tun"
|
"github.com/qdm12/gluetun/internal/tun"
|
||||||
updater "github.com/qdm12/gluetun/internal/updater/loop"
|
"github.com/qdm12/gluetun/internal/updater"
|
||||||
"github.com/qdm12/gluetun/internal/updater/resolver"
|
|
||||||
"github.com/qdm12/gluetun/internal/updater/unzip"
|
|
||||||
"github.com/qdm12/gluetun/internal/vpn"
|
"github.com/qdm12/gluetun/internal/vpn"
|
||||||
"github.com/qdm12/golibs/command"
|
"github.com/qdm12/golibs/command"
|
||||||
|
"github.com/qdm12/golibs/logging"
|
||||||
"github.com/qdm12/goshutdown"
|
"github.com/qdm12/goshutdown"
|
||||||
"github.com/qdm12/goshutdown/goroutine"
|
"github.com/qdm12/goshutdown/goroutine"
|
||||||
"github.com/qdm12/goshutdown/group"
|
"github.com/qdm12/goshutdown/group"
|
||||||
"github.com/qdm12/goshutdown/order"
|
"github.com/qdm12/goshutdown/order"
|
||||||
"github.com/qdm12/gosplash"
|
"github.com/qdm12/gosplash"
|
||||||
"github.com/qdm12/log"
|
|
||||||
"github.com/qdm12/updated/pkg/dnscrypto"
|
"github.com/qdm12/updated/pkg/dnscrypto"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -61,6 +55,11 @@ var (
|
|||||||
created = "an unknown date"
|
created = "an unknown date"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errSetupRouting = errors.New("cannot setup routing")
|
||||||
|
errCreateUser = errors.New("cannot create user")
|
||||||
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
buildInfo := models.BuildInformation{
|
buildInfo := models.BuildInformation{
|
||||||
Version: version,
|
Version: version,
|
||||||
@@ -69,36 +68,37 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
background := context.Background()
|
background := context.Background()
|
||||||
signalCh := make(chan os.Signal, 1)
|
signalCtx, stop := signal.NotifyContext(background, syscall.SIGINT, syscall.SIGTERM, os.Interrupt)
|
||||||
signal.Notify(signalCh, os.Interrupt, syscall.SIGTERM)
|
|
||||||
ctx, cancel := context.WithCancel(background)
|
ctx, cancel := context.WithCancel(background)
|
||||||
|
|
||||||
logger := log.New(log.SetLevel(log.LevelInfo))
|
logger := logging.New(logging.Settings{
|
||||||
|
Level: logging.LevelInfo,
|
||||||
|
})
|
||||||
|
|
||||||
args := os.Args
|
args := os.Args
|
||||||
tun := tun.New()
|
tun := tun.New()
|
||||||
netLinkDebugLogger := logger.New(log.SetComponent("netlink"))
|
netLinker := netlink.New()
|
||||||
netLinker := netlink.New(netLinkDebugLogger)
|
|
||||||
cli := cli.New()
|
cli := cli.New()
|
||||||
cmder := command.NewCmder()
|
cmder := command.NewCmder()
|
||||||
|
|
||||||
secretsReader := secrets.New()
|
|
||||||
filesReader := files.New()
|
|
||||||
envReader := env.New(logger)
|
envReader := env.New(logger)
|
||||||
muxReader := mux.New(secretsReader, filesReader, envReader)
|
filesReader := files.New()
|
||||||
|
secretsReader := secrets.New()
|
||||||
|
muxReader := mux.New(envReader, filesReader, secretsReader)
|
||||||
|
|
||||||
errorCh := make(chan error)
|
errorCh := make(chan error)
|
||||||
go func() {
|
go func() {
|
||||||
errorCh <- _main(ctx, buildInfo, args, logger, muxReader, tun, netLinker, cmder, cli)
|
errorCh <- _main(ctx, buildInfo, args, logger, muxReader, tun, netLinker, cmder, cli)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
var err error
|
|
||||||
select {
|
select {
|
||||||
case signal := <-signalCh:
|
case <-signalCtx.Done():
|
||||||
|
stop()
|
||||||
fmt.Println("")
|
fmt.Println("")
|
||||||
logger.Warn("Caught OS signal " + signal.String() + ", shutting down")
|
logger.Warn("Caught OS signal, shutting down")
|
||||||
cancel()
|
cancel()
|
||||||
case err = <-errorCh:
|
case err := <-errorCh:
|
||||||
|
stop()
|
||||||
close(errorCh)
|
close(errorCh)
|
||||||
if err == nil { // expected exit such as healthcheck
|
if err == nil { // expected exit such as healthcheck
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
@@ -110,38 +110,27 @@ func main() {
|
|||||||
const shutdownGracePeriod = 5 * time.Second
|
const shutdownGracePeriod = 5 * time.Second
|
||||||
timer := time.NewTimer(shutdownGracePeriod)
|
timer := time.NewTimer(shutdownGracePeriod)
|
||||||
select {
|
select {
|
||||||
case shutdownErr := <-errorCh:
|
case <-errorCh:
|
||||||
if !timer.Stop() {
|
if !timer.Stop() {
|
||||||
<-timer.C
|
<-timer.C
|
||||||
}
|
}
|
||||||
if shutdownErr != nil {
|
|
||||||
logger.Warnf("Shutdown not completed gracefully: %s", shutdownErr)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Info("Shutdown successful")
|
logger.Info("Shutdown successful")
|
||||||
if err != nil {
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
os.Exit(0)
|
|
||||||
case <-timer.C:
|
case <-timer.C:
|
||||||
logger.Warn("Shutdown timed out")
|
logger.Warn("Shutdown timed out")
|
||||||
os.Exit(1)
|
|
||||||
case signal := <-signalCh:
|
|
||||||
logger.Warn("Caught OS signal " + signal.String() + ", forcing shut down")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
errCommandUnknown = errors.New("command is unknown")
|
errCommandUnknown = errors.New("command is unknown")
|
||||||
)
|
)
|
||||||
|
|
||||||
//nolint:gocognit,gocyclo,maintidx
|
//nolint:gocognit,gocyclo
|
||||||
func _main(ctx context.Context, buildInfo models.BuildInformation,
|
func _main(ctx context.Context, buildInfo models.BuildInformation,
|
||||||
args []string, logger log.LoggerInterface, source Source,
|
args []string, logger logging.ParentLogger, source sources.Source,
|
||||||
tun Tun, netLinker netLinker, cmder command.RunStarter,
|
tun tun.Interface, netLinker netlink.NetLinker, cmder command.RunStarter,
|
||||||
cli clier) error {
|
cli cli.CLIer) error {
|
||||||
if len(args) > 1 { // cli operation
|
if len(args) > 1 { // cli operation
|
||||||
switch args[1] {
|
switch args[1] {
|
||||||
case "healthcheck":
|
case "healthcheck":
|
||||||
@@ -149,7 +138,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
|||||||
case "clientkey":
|
case "clientkey":
|
||||||
return cli.ClientKey(args[2:])
|
return cli.ClientKey(args[2:])
|
||||||
case "openvpnconfig":
|
case "openvpnconfig":
|
||||||
return cli.OpenvpnConfig(logger, source, netLinker)
|
return cli.OpenvpnConfig(logger, source)
|
||||||
case "update":
|
case "update":
|
||||||
return cli.Update(ctx, args[2:], logger)
|
return cli.Update(ctx, args[2:], logger)
|
||||||
case "format-servers":
|
case "format-servers":
|
||||||
@@ -159,7 +148,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
announcementExp, err := time.Parse(time.RFC3339, "2023-07-01T00:00:00Z")
|
announcementExp, err := time.Parse(time.RFC3339, "2021-02-15T00:00:00Z")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -170,7 +159,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
|||||||
Version: buildInfo.Version,
|
Version: buildInfo.Version,
|
||||||
Commit: buildInfo.Commit,
|
Commit: buildInfo.Commit,
|
||||||
BuildDate: buildInfo.Created,
|
BuildDate: buildInfo.Created,
|
||||||
Announcement: "Wiki moved to https://github.com/qdm12/gluetun-wiki",
|
Announcement: "Large settings parsing refactoring merged on 2022-01-06, please report any issue!",
|
||||||
AnnounceExp: announcementExp,
|
AnnounceExp: announcementExp,
|
||||||
// Sponsor information
|
// Sponsor information
|
||||||
PaypalUser: "qmcgaw",
|
PaypalUser: "qmcgaw",
|
||||||
@@ -185,68 +174,21 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: no need to validate minimal settings for the firewall:
|
|
||||||
// - global log level is parsed from source
|
|
||||||
// - firewall Debug and Enabled are booleans parsed from source
|
|
||||||
|
|
||||||
logger.Patch(log.SetLevel(*allSettings.Log.Level))
|
|
||||||
netLinker.PatchLoggerLevel(*allSettings.Log.Level)
|
|
||||||
|
|
||||||
routingLogger := logger.New(log.SetComponent("routing"))
|
|
||||||
if *allSettings.Firewall.Debug { // To remove in v4
|
|
||||||
routingLogger.Patch(log.SetLevel(log.LevelDebug))
|
|
||||||
}
|
|
||||||
routingConf := routing.New(netLinker, routingLogger)
|
|
||||||
|
|
||||||
defaultRoutes, err := routingConf.DefaultRoutes()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
localNetworks, err := routingConf.LocalNetworks()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
firewallLogger := logger.New(log.SetComponent("firewall"))
|
|
||||||
if *allSettings.Firewall.Debug { // To remove in v4
|
|
||||||
firewallLogger.Patch(log.SetLevel(log.LevelDebug))
|
|
||||||
}
|
|
||||||
firewallConf, err := firewall.NewConfig(ctx, firewallLogger, cmder,
|
|
||||||
defaultRoutes, localNetworks)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if *allSettings.Firewall.Enabled {
|
|
||||||
err = firewallConf.SetEnabled(ctx, true)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO run this in a loop or in openvpn to reload from file without restarting
|
// TODO run this in a loop or in openvpn to reload from file without restarting
|
||||||
storageLogger := logger.New(log.SetComponent("storage"))
|
storageLogger := logger.NewChild(logging.Settings{Prefix: "storage: "})
|
||||||
storage, err := storage.New(storageLogger, constants.ServersData)
|
storage, err := storage.New(storageLogger, constants.ServersData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
ipv6Supported, err := netLinker.IsIPv6Supported()
|
allServers := storage.GetServers()
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("checking for IPv6 support: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = allSettings.Validate(storage, ipv6Supported)
|
err = allSettings.Validate(allServers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
allSettings.Pprof.HTTPServer.Logger = logger.New(log.SetComponent("pprof"))
|
logger.PatchLevel(*allSettings.Log.Level)
|
||||||
pprofServer, err := pprof.New(allSettings.Pprof)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("creating Pprof server: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
puid, pgid := int(*allSettings.System.PUID), int(*allSettings.System.PGID)
|
puid, pgid := int(*allSettings.System.PUID), int(*allSettings.System.PGID)
|
||||||
|
|
||||||
@@ -255,7 +197,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
|||||||
// Create configurators
|
// Create configurators
|
||||||
alpineConf := alpine.New()
|
alpineConf := alpine.New()
|
||||||
ovpnConf := openvpn.New(
|
ovpnConf := openvpn.New(
|
||||||
logger.New(log.SetComponent("openvpn configurator")),
|
logger.NewChild(logging.Settings{Prefix: "openvpn configurator: "}),
|
||||||
cmder, puid, pgid)
|
cmder, puid, pgid)
|
||||||
dnsCrypto := dnscrypto.New(httpClient, "", "")
|
dnsCrypto := dnscrypto.New(httpClient, "", "")
|
||||||
const cacertsPath = "/etc/ssl/certs/ca-certificates.crt"
|
const cacertsPath = "/etc/ssl/certs/ca-certificates.crt"
|
||||||
@@ -264,8 +206,8 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
|||||||
|
|
||||||
err = printVersions(ctx, logger, []printVersionElement{
|
err = printVersions(ctx, logger, []printVersionElement{
|
||||||
{name: "Alpine", getVersion: alpineConf.Version},
|
{name: "Alpine", getVersion: alpineConf.Version},
|
||||||
|
{name: "OpenVPN 2.4", getVersion: ovpnConf.Version24},
|
||||||
{name: "OpenVPN 2.5", getVersion: ovpnConf.Version25},
|
{name: "OpenVPN 2.5", getVersion: ovpnConf.Version25},
|
||||||
{name: "OpenVPN 2.6", getVersion: ovpnConf.Version26},
|
|
||||||
{name: "Unbound", getVersion: dnsConf.Version},
|
{name: "Unbound", getVersion: dnsConf.Version},
|
||||||
{name: "IPtables", getVersion: func(ctx context.Context) (version string, err error) {
|
{name: "IPtables", getVersion: func(ctx context.Context) (version string, err error) {
|
||||||
return firewall.Version(ctx, cmder)
|
return firewall.Version(ctx, cmder)
|
||||||
@@ -277,10 +219,6 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
|||||||
|
|
||||||
logger.Info(allSettings.String())
|
logger.Info(allSettings.String())
|
||||||
|
|
||||||
for _, warning := range allSettings.Warnings() {
|
|
||||||
logger.Warn(warning)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := os.MkdirAll("/tmp/gluetun", 0644); err != nil {
|
if err := os.MkdirAll("/tmp/gluetun", 0644); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -291,7 +229,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
|||||||
const defaultUsername = "nonrootuser"
|
const defaultUsername = "nonrootuser"
|
||||||
nonRootUsername, err := alpineConf.CreateUser(defaultUsername, puid)
|
nonRootUsername, err := alpineConf.CreateUser(defaultUsername, puid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("creating user: %w", err)
|
return fmt.Errorf("%w: %s", errCreateUser, err)
|
||||||
}
|
}
|
||||||
if nonRootUsername != defaultUsername {
|
if nonRootUsername != defaultUsername {
|
||||||
logger.Info("using existing username " + nonRootUsername + " corresponding to user id " + fmt.Sprint(puid))
|
logger.Info("using existing username " + nonRootUsername + " corresponding to user id " + fmt.Sprint(puid))
|
||||||
@@ -299,22 +237,54 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
|||||||
// set it for Unbound
|
// set it for Unbound
|
||||||
// TODO remove this when migrating to qdm12/dns v2
|
// TODO remove this when migrating to qdm12/dns v2
|
||||||
allSettings.DNS.DoT.Unbound.Username = nonRootUsername
|
allSettings.DNS.DoT.Unbound.Username = nonRootUsername
|
||||||
allSettings.VPN.OpenVPN.ProcessUser = nonRootUsername
|
allSettings.VPN.OpenVPN.ProcUser = nonRootUsername
|
||||||
|
|
||||||
if err := os.Chown("/etc/unbound", puid, pgid); err != nil {
|
if err := os.Chown("/etc/unbound", puid, pgid); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
firewallLogLevel := *allSettings.Log.Level
|
||||||
|
if *allSettings.Firewall.Debug {
|
||||||
|
firewallLogLevel = logging.LevelDebug
|
||||||
|
}
|
||||||
|
routingLogger := logger.NewChild(logging.Settings{
|
||||||
|
Prefix: "routing: ",
|
||||||
|
Level: firewallLogLevel,
|
||||||
|
})
|
||||||
|
routingConf := routing.New(netLinker, routingLogger)
|
||||||
|
|
||||||
|
defaultInterface, defaultGateway, err := routingConf.DefaultRoute()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
localNetworks, err := routingConf.LocalNetworks()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultIP, err := routingConf.DefaultIP()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
firewallLogger := logger.NewChild(logging.Settings{
|
||||||
|
Prefix: "firewall: ",
|
||||||
|
Level: firewallLogLevel,
|
||||||
|
})
|
||||||
|
firewallConf := firewall.NewConfig(firewallLogger, cmder,
|
||||||
|
defaultInterface, defaultGateway, localNetworks, defaultIP)
|
||||||
|
|
||||||
if err := routingConf.Setup(); err != nil {
|
if err := routingConf.Setup(); err != nil {
|
||||||
if strings.Contains(err.Error(), "operation not permitted") {
|
if strings.Contains(err.Error(), "operation not permitted") {
|
||||||
logger.Warn("💡 Tip: Are you passing NET_ADMIN capability to gluetun?")
|
logger.Warn("💡 Tip: Are you passing NET_ADMIN capability to gluetun?")
|
||||||
}
|
}
|
||||||
return fmt.Errorf("setting up routing: %w", err)
|
return fmt.Errorf("%w: %s", errSetupRouting, err)
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
routingLogger.Info("routing cleanup...")
|
logger.Info("routing cleanup...")
|
||||||
if err := routingConf.TearDown(); err != nil {
|
if err := routingConf.TearDown(); err != nil {
|
||||||
routingLogger.Error("cannot teardown routing: " + err.Error())
|
logger.Error("cannot teardown routing: " + err.Error())
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -325,26 +295,25 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = routingConf.AddLocalRules(localNetworks)
|
if err := tun.Check(constants.TunnelDevice); err != nil {
|
||||||
if err != nil {
|
logger.Info(err.Error() + "; creating it...")
|
||||||
return fmt.Errorf("adding local rules: %w", err)
|
err = tun.Create(constants.TunnelDevice)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const tunDevice = "/dev/net/tun"
|
if *allSettings.Firewall.Enabled {
|
||||||
if err := tun.Check(tunDevice); err != nil {
|
err := firewallConf.SetEnabled(ctx, true) // disabled by default
|
||||||
logger.Info(err.Error() + "; creating it...")
|
|
||||||
err = tun.Create(tunDevice)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, port := range allSettings.Firewall.InputPorts {
|
for _, port := range allSettings.Firewall.InputPorts {
|
||||||
for _, defaultRoute := range defaultRoutes {
|
err = firewallConf.SetAllowedPort(ctx, port, defaultInterface)
|
||||||
err = firewallConf.SetAllowedPort(ctx, port, defaultRoute.NetInterface)
|
if err != nil {
|
||||||
if err != nil {
|
return err
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} // TODO move inside firewall?
|
} // TODO move inside firewall?
|
||||||
|
|
||||||
@@ -365,24 +334,14 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
|||||||
tickersGroupHandler := goshutdown.NewGroupHandler("tickers", defaultGroupOptions...)
|
tickersGroupHandler := goshutdown.NewGroupHandler("tickers", defaultGroupOptions...)
|
||||||
otherGroupHandler := goshutdown.NewGroupHandler("other", defaultGroupOptions...)
|
otherGroupHandler := goshutdown.NewGroupHandler("other", defaultGroupOptions...)
|
||||||
|
|
||||||
if *allSettings.Pprof.Enabled {
|
portForwardLogger := logger.NewChild(logging.Settings{Prefix: "port forwarding: "})
|
||||||
// TODO run in run loop so this can be patched at runtime
|
|
||||||
pprofReady := make(chan struct{})
|
|
||||||
pprofHandler, pprofCtx, pprofDone := goshutdown.NewGoRoutineHandler("pprof server")
|
|
||||||
go pprofServer.Run(pprofCtx, pprofReady, pprofDone)
|
|
||||||
otherGroupHandler.Add(pprofHandler)
|
|
||||||
<-pprofReady
|
|
||||||
}
|
|
||||||
|
|
||||||
portForwardLogger := logger.New(log.SetComponent("port forwarding"))
|
|
||||||
portForwardLooper := portforward.NewLoop(allSettings.VPN.Provider.PortForwarding,
|
portForwardLooper := portforward.NewLoop(allSettings.VPN.Provider.PortForwarding,
|
||||||
routingConf, httpClient, firewallConf, portForwardLogger, puid, pgid)
|
httpClient, firewallConf, portForwardLogger)
|
||||||
portForwardRunError, err := portForwardLooper.Start(ctx)
|
portForwardHandler, portForwardCtx, portForwardDone := goshutdown.NewGoRoutineHandler(
|
||||||
if err != nil {
|
"port forwarding", goroutine.OptionTimeout(time.Second))
|
||||||
return fmt.Errorf("starting port forwarding loop: %w", err)
|
go portForwardLooper.Run(portForwardCtx, portForwardDone)
|
||||||
}
|
|
||||||
|
|
||||||
unboundLogger := logger.New(log.SetComponent("dns"))
|
unboundLogger := logger.NewChild(logging.Settings{Prefix: "dns over tls: "})
|
||||||
unboundLooper := dns.NewLoop(dnsConf, allSettings.DNS, httpClient,
|
unboundLooper := dns.NewLoop(dnsConf, allSettings.DNS, httpClient,
|
||||||
unboundLogger)
|
unboundLogger)
|
||||||
dnsHandler, dnsCtx, dnsDone := goshutdown.NewGoRoutineHandler(
|
dnsHandler, dnsCtx, dnsDone := goshutdown.NewGoRoutineHandler(
|
||||||
@@ -396,38 +355,31 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
|||||||
go unboundLooper.RunRestartTicker(dnsTickerCtx, dnsTickerDone)
|
go unboundLooper.RunRestartTicker(dnsTickerCtx, dnsTickerDone)
|
||||||
controlGroupHandler.Add(dnsTickerHandler)
|
controlGroupHandler.Add(dnsTickerHandler)
|
||||||
|
|
||||||
publicipAPI, _ := pubipapi.ParseProvider(allSettings.PublicIP.API)
|
publicIPLooper := publicip.NewLoop(httpClient,
|
||||||
ipFetcher, err := pubipapi.New(publicipAPI, httpClient, *allSettings.PublicIP.APIToken)
|
logger.NewChild(logging.Settings{Prefix: "ip getter: "}),
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("creating public IP API client: %w", err)
|
|
||||||
}
|
|
||||||
publicIPLooper := publicip.NewLoop(ipFetcher,
|
|
||||||
logger.New(log.SetComponent("ip getter")),
|
|
||||||
allSettings.PublicIP, puid, pgid)
|
allSettings.PublicIP, puid, pgid)
|
||||||
publicIPRunError, err := publicIPLooper.Start(ctx)
|
pubIPHandler, pubIPCtx, pubIPDone := goshutdown.NewGoRoutineHandler(
|
||||||
if err != nil {
|
"public IP", goroutine.OptionTimeout(defaultShutdownTimeout))
|
||||||
return fmt.Errorf("starting public ip loop: %w", err)
|
go publicIPLooper.Run(pubIPCtx, pubIPDone)
|
||||||
}
|
otherGroupHandler.Add(pubIPHandler)
|
||||||
|
|
||||||
updaterLogger := logger.New(log.SetComponent("updater"))
|
pubIPTickerHandler, pubIPTickerCtx, pubIPTickerDone := goshutdown.NewGoRoutineHandler(
|
||||||
|
"public IP", goroutine.OptionTimeout(defaultShutdownTimeout))
|
||||||
|
go publicIPLooper.RunRestartTicker(pubIPTickerCtx, pubIPTickerDone)
|
||||||
|
tickersGroupHandler.Add(pubIPTickerHandler)
|
||||||
|
|
||||||
unzipper := unzip.New(httpClient)
|
vpnLogger := logger.NewChild(logging.Settings{Prefix: "vpn: "})
|
||||||
parallelResolver := resolver.NewParallelResolver(allSettings.Updater.DNSAddress)
|
vpnLooper := vpn.NewLoop(allSettings.VPN, allSettings.Firewall.VPNInputPorts,
|
||||||
openvpnFileExtractor := extract.New()
|
allServers, ovpnConf, netLinker, firewallConf, routingConf, portForwardLooper,
|
||||||
providers := provider.NewProviders(storage, time.Now, updaterLogger,
|
|
||||||
httpClient, unzipper, parallelResolver, ipFetcher, openvpnFileExtractor)
|
|
||||||
|
|
||||||
vpnLogger := logger.New(log.SetComponent("vpn"))
|
|
||||||
vpnLooper := vpn.NewLoop(allSettings.VPN, ipv6Supported, allSettings.Firewall.VPNInputPorts,
|
|
||||||
providers, storage, ovpnConf, netLinker, firewallConf, routingConf, portForwardLooper,
|
|
||||||
cmder, publicIPLooper, unboundLooper, vpnLogger, httpClient,
|
cmder, publicIPLooper, unboundLooper, vpnLogger, httpClient,
|
||||||
buildInfo, *allSettings.Version.Enabled)
|
buildInfo, *allSettings.Version.Enabled)
|
||||||
vpnHandler, vpnCtx, vpnDone := goshutdown.NewGoRoutineHandler(
|
vpnHandler, vpnCtx, vpnDone := goshutdown.NewGoRoutineHandler(
|
||||||
"vpn", goroutine.OptionTimeout(time.Second))
|
"vpn", goroutine.OptionTimeout(time.Second))
|
||||||
go vpnLooper.Run(vpnCtx, vpnDone)
|
go vpnLooper.Run(vpnCtx, vpnDone)
|
||||||
|
|
||||||
updaterLooper := updater.NewLoop(allSettings.Updater,
|
updaterLooper := updater.NewLooper(allSettings.Updater,
|
||||||
providers, storage, httpClient, updaterLogger)
|
allServers, storage, vpnLooper.SetServers, httpClient,
|
||||||
|
logger.NewChild(logging.Settings{Prefix: "updater: "}))
|
||||||
updaterHandler, updaterCtx, updaterDone := goshutdown.NewGoRoutineHandler(
|
updaterHandler, updaterCtx, updaterDone := goshutdown.NewGoRoutineHandler(
|
||||||
"updater", goroutine.OptionTimeout(defaultShutdownTimeout))
|
"updater", goroutine.OptionTimeout(defaultShutdownTimeout))
|
||||||
// wait for updaterLooper.Restart() or its ticket launched with RunRestartTicker
|
// wait for updaterLooper.Restart() or its ticket launched with RunRestartTicker
|
||||||
@@ -440,37 +392,31 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
|||||||
controlGroupHandler.Add(updaterTickerHandler)
|
controlGroupHandler.Add(updaterTickerHandler)
|
||||||
|
|
||||||
httpProxyLooper := httpproxy.NewLoop(
|
httpProxyLooper := httpproxy.NewLoop(
|
||||||
logger.New(log.SetComponent("http proxy")),
|
logger.NewChild(logging.Settings{Prefix: "http proxy: "}),
|
||||||
allSettings.HTTPProxy)
|
allSettings.HTTPProxy)
|
||||||
httpProxyHandler, httpProxyCtx, httpProxyDone := goshutdown.NewGoRoutineHandler(
|
httpProxyHandler, httpProxyCtx, httpProxyDone := goshutdown.NewGoRoutineHandler(
|
||||||
"http proxy", goroutine.OptionTimeout(defaultShutdownTimeout))
|
"http proxy", goroutine.OptionTimeout(defaultShutdownTimeout))
|
||||||
go httpProxyLooper.Run(httpProxyCtx, httpProxyDone)
|
go httpProxyLooper.Run(httpProxyCtx, httpProxyDone)
|
||||||
otherGroupHandler.Add(httpProxyHandler)
|
otherGroupHandler.Add(httpProxyHandler)
|
||||||
|
|
||||||
shadowsocksLooper := shadowsocks.NewLoop(allSettings.Shadowsocks,
|
shadowsocksLooper := shadowsocks.NewLooper(allSettings.Shadowsocks,
|
||||||
logger.New(log.SetComponent("shadowsocks")))
|
logger.NewChild(logging.Settings{Prefix: "shadowsocks: "}))
|
||||||
shadowsocksHandler, shadowsocksCtx, shadowsocksDone := goshutdown.NewGoRoutineHandler(
|
shadowsocksHandler, shadowsocksCtx, shadowsocksDone := goshutdown.NewGoRoutineHandler(
|
||||||
"shadowsocks proxy", goroutine.OptionTimeout(defaultShutdownTimeout))
|
"shadowsocks proxy", goroutine.OptionTimeout(defaultShutdownTimeout))
|
||||||
go shadowsocksLooper.Run(shadowsocksCtx, shadowsocksDone)
|
go shadowsocksLooper.Run(shadowsocksCtx, shadowsocksDone)
|
||||||
otherGroupHandler.Add(shadowsocksHandler)
|
otherGroupHandler.Add(shadowsocksHandler)
|
||||||
|
|
||||||
controlServerAddress := *allSettings.ControlServer.Address
|
controlServerAddress := fmt.Sprintf(":%d", *allSettings.ControlServer.Port)
|
||||||
controlServerLogging := *allSettings.ControlServer.Log
|
controlServerLogging := *allSettings.ControlServer.Log
|
||||||
httpServerHandler, httpServerCtx, httpServerDone := goshutdown.NewGoRoutineHandler(
|
httpServerHandler, httpServerCtx, httpServerDone := goshutdown.NewGoRoutineHandler(
|
||||||
"http server", goroutine.OptionTimeout(defaultShutdownTimeout))
|
"http server", goroutine.OptionTimeout(defaultShutdownTimeout))
|
||||||
httpServer, err := server.New(httpServerCtx, controlServerAddress, controlServerLogging,
|
httpServer := server.New(httpServerCtx, controlServerAddress, controlServerLogging,
|
||||||
logger.New(log.SetComponent("http server")),
|
logger.NewChild(logging.Settings{Prefix: "http server: "}),
|
||||||
buildInfo, vpnLooper, portForwardLooper, unboundLooper, updaterLooper, publicIPLooper,
|
buildInfo, vpnLooper, portForwardLooper, unboundLooper, updaterLooper, publicIPLooper)
|
||||||
storage, ipv6Supported)
|
go httpServer.Run(httpServerCtx, httpServerDone)
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("setting up control server: %w", err)
|
|
||||||
}
|
|
||||||
httpServerReady := make(chan struct{})
|
|
||||||
go httpServer.Run(httpServerCtx, httpServerReady, httpServerDone)
|
|
||||||
<-httpServerReady
|
|
||||||
controlGroupHandler.Add(httpServerHandler)
|
controlGroupHandler.Add(httpServerHandler)
|
||||||
|
|
||||||
healthLogger := logger.New(log.SetComponent("healthcheck"))
|
healthLogger := logger.NewChild(logging.Settings{Prefix: "healthcheck: "})
|
||||||
healthcheckServer := healthcheck.NewServer(allSettings.Health, healthLogger, vpnLooper)
|
healthcheckServer := healthcheck.NewServer(allSettings.Health, healthLogger, vpnLooper)
|
||||||
healthServerHandler, healthServerCtx, healthServerDone := goshutdown.NewGoRoutineHandler(
|
healthServerHandler, healthServerCtx, healthServerDone := goshutdown.NewGoRoutineHandler(
|
||||||
"HTTP health server", goroutine.OptionTimeout(defaultShutdownTimeout))
|
"HTTP health server", goroutine.OptionTimeout(defaultShutdownTimeout))
|
||||||
@@ -481,31 +427,13 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
|||||||
order.OptionOnSuccess(defaultShutdownOnSuccess),
|
order.OptionOnSuccess(defaultShutdownOnSuccess),
|
||||||
order.OptionOnFailure(defaultShutdownOnFailure))
|
order.OptionOnFailure(defaultShutdownOnFailure))
|
||||||
orderHandler.Append(controlGroupHandler, tickersGroupHandler, healthServerHandler,
|
orderHandler.Append(controlGroupHandler, tickersGroupHandler, healthServerHandler,
|
||||||
vpnHandler, otherGroupHandler)
|
vpnHandler, portForwardHandler, otherGroupHandler)
|
||||||
|
|
||||||
// Start VPN for the first time in a blocking call
|
// Start VPN for the first time in a blocking call
|
||||||
// until the VPN is launched
|
// until the VPN is launched
|
||||||
_, _ = vpnLooper.ApplyStatus(ctx, constants.Running) // TODO option to disable with variable
|
_, _ = vpnLooper.ApplyStatus(ctx, constants.Running) // TODO option to disable with variable
|
||||||
|
|
||||||
select {
|
<-ctx.Done()
|
||||||
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())
|
return orderHandler.Shutdown(context.Background())
|
||||||
}
|
}
|
||||||
@@ -528,68 +456,10 @@ func printVersions(ctx context.Context, logger infoer,
|
|||||||
for _, element := range elements {
|
for _, element := range elements {
|
||||||
version, err := element.getVersion(ctx)
|
version, err := element.getVersion(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("getting %s version: %w", element.name, err)
|
return err
|
||||||
}
|
}
|
||||||
logger.Info(element.name + " version: " + version)
|
logger.Info(element.name + " version: " + version)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type netLinker interface {
|
|
||||||
Addresser
|
|
||||||
Router
|
|
||||||
Ruler
|
|
||||||
Linker
|
|
||||||
IsWireguardSupported() (ok bool, err error)
|
|
||||||
IsIPv6Supported() (ok bool, err error)
|
|
||||||
PatchLoggerLevel(level log.Level)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Addresser interface {
|
|
||||||
AddrList(link netlink.Link, family int) (
|
|
||||||
addresses []netlink.Addr, err error)
|
|
||||||
AddrReplace(link netlink.Link, addr netlink.Addr) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type Router interface {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
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) (linkIndex int, err error)
|
|
||||||
LinkDel(link netlink.Link) (err error)
|
|
||||||
LinkSetUp(link netlink.Link) (linkIndex int, err error)
|
|
||||||
LinkSetDown(link netlink.Link) (err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type clier interface {
|
|
||||||
ClientKey(args []string) error
|
|
||||||
FormatServers(args []string) error
|
|
||||||
OpenvpnConfig(logger cli.OpenvpnConfigLogger, source cli.Source, ipv6Checker cli.IPv6Checker) error
|
|
||||||
HealthCheck(ctx context.Context, source cli.Source, warner cli.Warner) error
|
|
||||||
Update(ctx context.Context, args []string, logger cli.UpdaterLogger) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type Tun interface {
|
|
||||||
Check(tunDevice string) error
|
|
||||||
Create(tunDevice string) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type Source interface {
|
|
||||||
Read() (settings settings.Settings, err error)
|
|
||||||
ReadHealth() (health settings.Health, err error)
|
|
||||||
String() string
|
|
||||||
}
|
|
||||||
|
|||||||
56
go.mod
56
go.mod
@@ -1,55 +1,43 @@
|
|||||||
module github.com/qdm12/gluetun
|
module github.com/qdm12/gluetun
|
||||||
|
|
||||||
go 1.21
|
go 1.17
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/breml/rootcerts v0.2.16
|
github.com/breml/rootcerts v0.2.1
|
||||||
github.com/fatih/color v1.16.0
|
github.com/fatih/color v1.13.0
|
||||||
github.com/golang/mock v1.6.0
|
github.com/golang/mock v1.6.0
|
||||||
github.com/klauspost/compress v1.17.4
|
|
||||||
github.com/klauspost/pgzip v1.2.6
|
|
||||||
github.com/qdm12/dns v1.11.0
|
github.com/qdm12/dns v1.11.0
|
||||||
github.com/qdm12/golibs v0.0.0-20210822203818-5c568b0777b6
|
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/goshutdown v0.3.0
|
||||||
github.com/qdm12/gosplash v0.1.0
|
github.com/qdm12/gosplash v0.1.0
|
||||||
github.com/qdm12/gotree v0.2.0
|
github.com/qdm12/gotree v0.2.0
|
||||||
github.com/qdm12/govalid v0.2.0-rc1
|
github.com/qdm12/govalid v0.1.0
|
||||||
github.com/qdm12/log v0.1.0
|
github.com/qdm12/ss-server v0.4.0
|
||||||
github.com/qdm12/ss-server v0.5.0
|
|
||||||
github.com/qdm12/updated v0.0.0-20210603204757-205acfe6937e
|
github.com/qdm12/updated v0.0.0-20210603204757-205acfe6937e
|
||||||
github.com/stretchr/testify v1.8.4
|
github.com/stretchr/testify v1.7.0
|
||||||
github.com/ulikunitz/xz v0.5.11
|
github.com/vishvananda/netlink v1.1.1-0.20211129163951-9ada19101fc5
|
||||||
github.com/vishvananda/netlink v1.2.1-beta.2
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c
|
||||||
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a
|
golang.zx2c4.com/wireguard v0.0.0-20210805125648-3957e9b9dd19
|
||||||
golang.org/x/net v0.19.0
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20210803171230-4253848d036c
|
||||||
golang.org/x/sys v0.15.0
|
inet.af/netaddr v0.0.0-20210718074554-06ca8145d722
|
||||||
golang.org/x/text v0.14.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
|
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/google/go-cmp v0.5.9 // indirect
|
github.com/google/go-cmp v0.5.5 // indirect
|
||||||
github.com/josharian/native v1.0.0 // indirect
|
github.com/josharian/native v0.0.0-20200817173448-b6b71def0850 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
github.com/mattn/go-colorable v0.1.9 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||||
github.com/mdlayher/genetlink v1.2.0 // indirect
|
github.com/mdlayher/genetlink v1.0.0 // indirect
|
||||||
github.com/mdlayher/netlink v1.6.2 // indirect
|
github.com/mdlayher/netlink v1.4.0 // indirect
|
||||||
github.com/mdlayher/socket v0.2.3 // indirect
|
|
||||||
github.com/miekg/dns v1.1.40 // indirect
|
github.com/miekg/dns v1.1.40 // indirect
|
||||||
github.com/mr-tron/base58 v1.2.0 // indirect
|
github.com/mr-tron/base58 v1.2.0 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
|
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
|
||||||
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae // indirect
|
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae // indirect
|
||||||
go4.org/intern v0.0.0-20211027215823-ae77deb06f29 // indirect
|
go4.org/intern v0.0.0-20210108033219-3eb7198706b2 // indirect
|
||||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20230525183740-e7c30c78aeb2 // indirect
|
go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222180813-1025295fd063 // indirect
|
||||||
golang.org/x/crypto v0.17.0 // indirect
|
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect
|
||||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect
|
golang.org/x/net v0.0.0-20210504132125-bbd867fde50d // indirect
|
||||||
golang.org/x/sync v0.1.0 // indirect
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
|
||||||
)
|
)
|
||||||
|
|||||||
185
go.sum
185
go.sum
@@ -4,8 +4,8 @@ github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/g
|
|||||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
||||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||||
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
||||||
github.com/breml/rootcerts v0.2.16 h1:yN1TGvicfHx8dKz3OQRIrx/5nE/iN3XT1ibqGbd6urc=
|
github.com/breml/rootcerts v0.2.1 h1:GZMVDXOs945764NFck0vtHSjktKYubOFM0kjf5HAuwc=
|
||||||
github.com/breml/rootcerts v0.2.16/go.mod h1:S/PKh+4d1HUn4HQovEB8hPJZO6pUZYrIhmXBhsegfXw=
|
github.com/breml/rootcerts v0.2.1/go.mod h1:24FDtzYMpqIeYC7QzaE8VPRQaFZU5TIUDlyk8qwjD88=
|
||||||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
@@ -14,8 +14,8 @@ github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDD
|
|||||||
github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw=
|
github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw=
|
||||||
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
|
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
|
||||||
github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
||||||
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
|
||||||
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||||
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
||||||
github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
||||||
@@ -36,24 +36,29 @@ github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3K
|
|||||||
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
||||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
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/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
|
||||||
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
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.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.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||||
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/gotify/go-api-client/v2 v2.0.4/go.mod h1:VKiah/UK20bXsr0JObE1eBVLW44zbBouzjuri9iwjFU=
|
github.com/gotify/go-api-client/v2 v2.0.4/go.mod h1:VKiah/UK20bXsr0JObE1eBVLW44zbBouzjuri9iwjFU=
|
||||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
||||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||||
github.com/josharian/native v1.0.0 h1:Ts/E8zCSEsG17dUqv7joXJFybuMLjQfWE04tsBODTxk=
|
github.com/josharian/native v0.0.0-20200817173448-b6b71def0850 h1:uhL5Gw7BINiiPAo24A2sxkcDI0Jt/sqp1v5xQCniEFA=
|
||||||
github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
github.com/josharian/native v0.0.0-20200817173448-b6b71def0850/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||||
|
github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw=
|
||||||
|
github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ=
|
||||||
|
github.com/jsimonetti/rtnetlink v0.0.0-20201009170750-9c6f07d100c1/go.mod h1:hqoO/u39cqLeBLebZ8fWdE96O7FxrAsRYhnVOdgHxok=
|
||||||
|
github.com/jsimonetti/rtnetlink v0.0.0-20201216134343-bde56ed16391/go.mod h1:cR77jAZG3Y3bsb8hF6fHJbFoyFukLFOkQ98S0pQz3xw=
|
||||||
|
github.com/jsimonetti/rtnetlink v0.0.0-20201220180245-69540ac93943/go.mod h1:z4c53zj6Eex712ROyh8WI0ihysb5j2ROyV42iNogmAs=
|
||||||
|
github.com/jsimonetti/rtnetlink v0.0.0-20210122163228-8d122574c736/go.mod h1:ZXpIyOK59ZnN7J0BV99cZUPmsqDRZ3eq5X+st7u/oSA=
|
||||||
|
github.com/jsimonetti/rtnetlink v0.0.0-20210212075122-66c871082f2b h1:c3NTyLNozICy8B4mlMXemD3z/gXgQzVXZS/HqT+i3do=
|
||||||
|
github.com/jsimonetti/rtnetlink v0.0.0-20210212075122-66c871082f2b/go.mod h1:8w9Rh8m+aHZIG69YPGGem1i5VzoyRC8nw2kA8B+ik5U=
|
||||||
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
||||||
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
|
|
||||||
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
|
|
||||||
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 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
@@ -63,20 +68,25 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
|||||||
github.com/kyokomi/emoji v2.2.4+incompatible/go.mod h1:mZ6aGCD7yk8j6QY6KICwnZ2pxoszVseX1DNoGtU2tBA=
|
github.com/kyokomi/emoji v2.2.4+incompatible/go.mod h1:mZ6aGCD7yk8j6QY6KICwnZ2pxoszVseX1DNoGtU2tBA=
|
||||||
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||||
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U=
|
||||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mdlayher/ethtool v0.0.0-20210210192532-2b88debcdd43 h1:WgyLFv10Ov49JAQI/ZLUkCZ7VJS3r74hwFIGXJsgZlY=
|
||||||
github.com/mdlayher/genetlink v1.2.0 h1:4yrIkRV5Wfk1WfpWTcoOlGmsWgQj3OtQN9ZsbrE+XtU=
|
github.com/mdlayher/ethtool v0.0.0-20210210192532-2b88debcdd43/go.mod h1:+t7E0lkKfbBsebllff1xdTmyJt8lH37niI6kwFk9OTo=
|
||||||
github.com/mdlayher/genetlink v1.2.0/go.mod h1:ra5LDov2KrUCZJiAtEvXXZBxGMInICMXIwshlJ+qRxQ=
|
github.com/mdlayher/genetlink v1.0.0 h1:OoHN1OdyEIkScEmRgxLEe2M9U8ClMytqA5niynLtfj0=
|
||||||
github.com/mdlayher/netlink v1.6.0/go.mod h1:0o3PlBmGst1xve7wQ7j/hwpNaFaH4qCRyWCdcZk8/vA=
|
github.com/mdlayher/genetlink v1.0.0/go.mod h1:0rJ0h4itni50A86M2kHcgS85ttZazNt7a8H2a2cw0Gc=
|
||||||
github.com/mdlayher/netlink v1.6.2 h1:D2zGSkvYsJ6NreeED3JiVTu1lj2sIYATqSaZlhPzUgQ=
|
github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA=
|
||||||
github.com/mdlayher/netlink v1.6.2/go.mod h1:O1HXX2sIWSMJ3Qn1BYZk1yZM+7iMki/uYGGiwGyq/iU=
|
github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M=
|
||||||
github.com/mdlayher/socket v0.1.1/go.mod h1:mYV5YIZAfHh4dzDVzI8x8tWLWCliuX8Mon5Awbj+qDs=
|
github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY=
|
||||||
github.com/mdlayher/socket v0.2.3 h1:XZA2X2TjdOwNoNPVPclRCURoX/hokBY8nkTmRZFEheM=
|
github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o=
|
||||||
github.com/mdlayher/socket v0.2.3/go.mod h1:bz12/FozYNH/VbvC3q7TRIK/Y6dH1kCKsXaUeXi/FmY=
|
github.com/mdlayher/netlink v1.2.0/go.mod h1:kwVW1io0AZy9A1E2YYgaD4Cj+C+GPkU6klXCMzIJ9p8=
|
||||||
|
github.com/mdlayher/netlink v1.2.1/go.mod h1:bacnNlfhqHqqLo4WsYeXSqfyXkInQ9JneWI68v1KwSU=
|
||||||
|
github.com/mdlayher/netlink v1.2.2-0.20210123213345-5cc92139ae3e/go.mod h1:bacnNlfhqHqqLo4WsYeXSqfyXkInQ9JneWI68v1KwSU=
|
||||||
|
github.com/mdlayher/netlink v1.3.0/go.mod h1:xK/BssKuwcRXHrtN04UBkwQ6dY9VviGGuriDdoPSWys=
|
||||||
|
github.com/mdlayher/netlink v1.4.0 h1:n3ARR+Fm0dDv37dj5wSWZXDKcy+U0zwcXS3zKMnSiT0=
|
||||||
|
github.com/mdlayher/netlink v1.4.0/go.mod h1:dRJi5IABcZpBD2A3D0Mv/AiX8I9uDEu5oGkAVrekmf8=
|
||||||
github.com/miekg/dns v1.1.40 h1:pyyPFfGMnciYUk/mXpKkVmeMQjfXqt3FAJ2hy7tPiLA=
|
github.com/miekg/dns v1.1.40 h1:pyyPFfGMnciYUk/mXpKkVmeMQjfXqt3FAJ2hy7tPiLA=
|
||||||
github.com/miekg/dns v1.1.40/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
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 h1:RlZweED6sbSArvlE924+mUcZuXKLBHA35U7LN621Bws=
|
||||||
@@ -97,20 +107,16 @@ 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-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 h1:bge5AL7cjHJMPz+5IOz5yF01q/l8No6+lIEBieA8gMg=
|
||||||
github.com/qdm12/golibs v0.0.0-20210822203818-5c568b0777b6/go.mod h1:6aRbg4Z/bTbm9JfxsGXfWKHi7zsOvPfUTK1S5HuAFKg=
|
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 h1:pqBpJkdwlZlfTEx4QHtS8u8CXx6pG0fVo6S1N0MpSEM=
|
||||||
github.com/qdm12/goshutdown v0.3.0/go.mod h1:EqZ46No00kCTZ5qzdd3qIzY6ayhMt24QI8Mh8LVQYmM=
|
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 h1:Sfl+zIjFZFP7b0iqf2l5UkmEY97XBnaKkH3FNY6Gf7g=
|
||||||
github.com/qdm12/gosplash v0.1.0/go.mod h1:+A3fWW4/rUeDXhY3ieBzwghKdnIPFJgD8K3qQkenJlw=
|
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 h1:+58ltxkNLUyHtATFereAcOjBVfY6ETqRex8XK90Fb/c=
|
||||||
github.com/qdm12/gotree v0.2.0/go.mod h1:1SdFaqKZuI46U1apbXIf25pDMNnrPuYLEqMF/qL4lY4=
|
github.com/qdm12/gotree v0.2.0/go.mod h1:1SdFaqKZuI46U1apbXIf25pDMNnrPuYLEqMF/qL4lY4=
|
||||||
github.com/qdm12/govalid v0.2.0-rc1 h1:4iYQvU4ibrASgzelsEgZX4JyKX3UTB/DcHObzQ7BXtw=
|
github.com/qdm12/govalid v0.1.0 h1:UIFVmuaAg0Q+h0GeyfcFEZ5sQ5KJPvRQwycC1/cqDN8=
|
||||||
github.com/qdm12/govalid v0.2.0-rc1/go.mod h1:/uWzVWMuS71wmbsVnlUxpQiy6EAXqm8eQ2RbyA72roQ=
|
github.com/qdm12/govalid v0.1.0/go.mod h1:CyS/OEQdOvunBgrtIsW93fjd4jBkwZPBjGSpxq3NwA4=
|
||||||
github.com/qdm12/log v0.1.0 h1:jYBd/xscHYpblzZAd2kjZp2YmuYHjAAfbTViJWxoPTw=
|
github.com/qdm12/ss-server v0.4.0 h1:lMMYfDGc9P86Lyvd3+p8lK4hhgHUKDzjZC91FqJYkDU=
|
||||||
github.com/qdm12/log v0.1.0/go.mod h1:Vchi5M8uBvHfPNIblN4mjXn/oSbiWguQIbsgF1zdQPI=
|
github.com/qdm12/ss-server v0.4.0/go.mod h1:AY0p4huvPUPW+/CiWsJcDgT6sneDryk26VXSccPNCxY=
|
||||||
github.com/qdm12/ss-server v0.5.0 h1:ARAqJayohDM51BmJ/R5Yplkpo+Qxgp7xizBF1HWd7uQ=
|
|
||||||
github.com/qdm12/ss-server v0.5.0/go.mod h1:eFd8PL/uy0ZvJ4KeSUzToruJctVQoYqXk+LRy9vcOiI=
|
|
||||||
github.com/qdm12/updated v0.0.0-20210603204757-205acfe6937e h1:4q+uFLawkaQRq3yARYLsjJPZd2wYwxn4g6G/5v0xW1g=
|
github.com/qdm12/updated v0.0.0-20210603204757-205acfe6937e h1:4q+uFLawkaQRq3yARYLsjJPZd2wYwxn4g6G/5v0xW1g=
|
||||||
github.com/qdm12/updated v0.0.0-20210603204757-205acfe6937e/go.mod h1:UvJRGkZ9XL3/D7e7JiTTVLm1F3Cymd3/gFpD6frEpBo=
|
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=
|
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
|
||||||
@@ -122,101 +128,97 @@ github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoH
|
|||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
|
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
github.com/vishvananda/netlink v1.1.1-0.20211129163951-9ada19101fc5 h1:b/k/BVWzWRS5v6AB0gf2ckFSbFsHN5jR0HoNso1pN+w=
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/vishvananda/netlink v1.1.1-0.20211129163951-9ada19101fc5/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
|
||||||
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=
|
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae h1:4hwBBUfQCFe3Cym0ZtKyq7L16eZUtYKs+BaHDN6mAns=
|
||||||
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
||||||
github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
|
github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
|
||||||
github.com/yl2chen/cidranger v1.0.2/go.mod h1:9U1yz7WPYDwf0vpNWFaeRh0bjwz5RVgRy/9UEQfHl0g=
|
github.com/yl2chen/cidranger v1.0.2/go.mod h1:9U1yz7WPYDwf0vpNWFaeRh0bjwz5RVgRy/9UEQfHl0g=
|
||||||
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a h1:fZHgsYlfvtyqToslyjUt3VOPF4J7aK/3MPcK7xp3PDk=
|
|
||||||
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a/go.mod h1:ul22v+Nro/R083muKhosV54bj5niojjWZvU8xrevuH4=
|
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
|
go4.org/intern v0.0.0-20210108033219-3eb7198706b2 h1:VFTf+jjIgsldaz/Mr00VaCSswHJrI2hIjQygE/W4IMg=
|
||||||
go4.org/intern v0.0.0-20210108033219-3eb7198706b2/go.mod h1:vLqJ+12kCw61iCWsPto0EOHhBS+o4rO5VIucbc9g2Cc=
|
go4.org/intern v0.0.0-20210108033219-3eb7198706b2/go.mod h1:vLqJ+12kCw61iCWsPto0EOHhBS+o4rO5VIucbc9g2Cc=
|
||||||
go4.org/intern v0.0.0-20211027215823-ae77deb06f29 h1:UXLjNohABv4S58tHmeuIZDO6e3mHpW2Dx33gaNt03LE=
|
|
||||||
go4.org/intern v0.0.0-20211027215823-ae77deb06f29/go.mod h1:cS2ma+47FKrLPdXFpr7CuxiTW3eyJbWew4qx0qtQWDA=
|
|
||||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222175341-b30ae309168e/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
|
go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222175341-b30ae309168e/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
|
||||||
|
go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222180813-1025295fd063 h1:1tk03FUNpulq2cuWpXZWj649rwJpk0d20rxWiopKRmc=
|
||||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222180813-1025295fd063/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
|
go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222180813-1025295fd063/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
|
||||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20211027215541-db492cf91b37/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
|
|
||||||
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-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-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||||
|
golang.org/x/crypto v0.0.0-20210503195802-e9a32991a82e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||||
|
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=
|
||||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
|
|
||||||
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
|
||||||
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.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.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
|
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
|
golang.org/x/net v0.0.0-20201216054612-986b41b23924/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||||
golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20210504132125-bbd867fde50d h1:nTDGCTeAu2LhcsHTRzjyIUbZHCJ4QePArsm27Hka0UM=
|
||||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
golang.org/x/net v0.0.0-20210504132125-bbd867fde50d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20220923203811-8be639271d50/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
|
||||||
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
|
|
||||||
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
|
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
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-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220923202941-7f9b1623fab7/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201118182958-a01c418693c7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201218084310-7d0127a74742/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210110051926-789bb1bd4061/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210123111255-9b0068b26619/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210216163648-f7da38b97c65/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210309040221-94ec62e08169/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210503173754-0981d6026fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
|
||||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||||
golang.org/x/sys v0.0.0-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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
|
||||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
|
||||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
|
||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
|
||||||
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-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
|
golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
@@ -226,29 +228,24 @@ golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
|||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
|
golang.zx2c4.com/wireguard v0.0.0-20210427022245-097af6e1351b/go.mod h1:a057zjmoc00UN7gVkaJt2sXVK523kMJcogDTEvPIasg=
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
golang.zx2c4.com/wireguard v0.0.0-20210805125648-3957e9b9dd19 h1:ab2jcw2W91Rz07eHAb8Lic7sFQKO0NhBftjv6m/gL/0=
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20230325221338-052af4a8072b h1:J1CaxgLerRR5lgx3wnr6L04cJFbWoceSK9JWBdglINo=
|
golang.zx2c4.com/wireguard v0.0.0-20210805125648-3957e9b9dd19/go.mod h1:laHzsbfMhGSobUmruXWAyMKKHSqvIcrqZJMyHD+/3O8=
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20230325221338-052af4a8072b/go.mod h1:tqur9LnfstdR9ep2LaJT4lFUl0EjlHtge+gAjmsHUG4=
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20210803171230-4253848d036c h1:ADNrRDI5NR23/TUCnEmlLZLt4u9DnZ2nwRkPrAcFvto=
|
||||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230215201556-9c5414ab4bde h1:ybF7AMzIUikL9x4LgwEmzhXtzRpKNqngme1VGDWz+Nk=
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20210803171230-4253848d036c/go.mod h1:+1XihzyZUBJcSc5WO9SwNA7v26puQwOEDwanaxfNXPQ=
|
||||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230215201556-9c5414ab4bde/go.mod h1:mQqgjkW8GQQcJQsbBvK890TKqUK1DfKWkuBGbOkuMHQ=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
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 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
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-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-fixtures.v3 v3.5.0/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g=
|
||||||
gopkg.in/src-d/go-git.v4 v4.13.1/go.mod h1:nx5NYcxdKxq5fpltdHnPa2Exj4Sx0EclMWZQbYDu2z8=
|
gopkg.in/src-d/go-git.v4 v4.13.1/go.mod h1:nx5NYcxdKxq5fpltdHnPa2Exj4Sx0EclMWZQbYDu2z8=
|
||||||
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
||||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
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-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-20210718074554-06ca8145d722 h1:Qws2rZnQudC58cIagVucPQDLmMi3kAXgxscsgD0v6DU=
|
||||||
inet.af/netaddr v0.0.0-20220811202034-502d2d690317/go.mod h1:OIezDfdzOgFhuw4HuWapWq2e9l0H9tK4F1j+ETRtF3k=
|
inet.af/netaddr v0.0.0-20210718074554-06ca8145d722/go.mod h1:z0nx+Dh+7N7CC8V5ayHtHGpZpxLQZZxkIaaz6HN65Ls=
|
||||||
|
|||||||
@@ -1,9 +1,17 @@
|
|||||||
|
// Package alpine defines a configurator to interact with the Alpine operating system.
|
||||||
package alpine
|
package alpine
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os/user"
|
"os/user"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var _ Alpiner = (*Alpine)(nil)
|
||||||
|
|
||||||
|
type Alpiner interface {
|
||||||
|
UserCreater
|
||||||
|
VersionGetter
|
||||||
|
}
|
||||||
|
|
||||||
type Alpine struct {
|
type Alpine struct {
|
||||||
alpineReleasePath string
|
alpineReleasePath string
|
||||||
passwdPath string
|
passwdPath string
|
||||||
|
|||||||
@@ -12,6 +12,10 @@ var (
|
|||||||
ErrUserAlreadyExists = errors.New("user already exists")
|
ErrUserAlreadyExists = errors.New("user already exists")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type UserCreater interface {
|
||||||
|
CreateUser(username string, uid int) (createdUsername string, err error)
|
||||||
|
}
|
||||||
|
|
||||||
// CreateUser creates a user in Alpine with the given UID.
|
// CreateUser creates a user in Alpine with the given UID.
|
||||||
func (a *Alpine) CreateUser(username string, uid int) (createdUsername string, err error) {
|
func (a *Alpine) CreateUser(username string, uid int) (createdUsername string, err error) {
|
||||||
UIDStr := strconv.Itoa(uid)
|
UIDStr := strconv.Itoa(uid)
|
||||||
|
|||||||
@@ -7,7 +7,11 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (a *Alpine) Version(context.Context) (version string, err error) {
|
type VersionGetter interface {
|
||||||
|
Version(ctx context.Context) (version string, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Alpine) Version(ctx context.Context) (version string, err error) {
|
||||||
file, err := os.OpenFile(a.alpineReleasePath, os.O_RDONLY, 0)
|
file, err := os.OpenFile(a.alpineReleasePath, os.O_RDONLY, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
|||||||
@@ -2,6 +2,6 @@ package cli
|
|||||||
|
|
||||||
import "context"
|
import "context"
|
||||||
|
|
||||||
func (c *CLI) CI(context.Context) error {
|
func (c *CLI) CI(context context.Context) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,16 @@
|
|||||||
|
// Package cli defines an interface CLI to run command line operations.
|
||||||
package cli
|
package cli
|
||||||
|
|
||||||
|
var _ CLIer = (*CLI)(nil)
|
||||||
|
|
||||||
|
type CLIer interface {
|
||||||
|
ClientKeyFormatter
|
||||||
|
HealthChecker
|
||||||
|
OpenvpnConfigMaker
|
||||||
|
Updater
|
||||||
|
ServersFormatter
|
||||||
|
}
|
||||||
|
|
||||||
type CLI struct {
|
type CLI struct {
|
||||||
repoServersPath string
|
repoServersPath string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,12 +7,16 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration/sources/files"
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type ClientKeyFormatter interface {
|
||||||
|
ClientKey(args []string) error
|
||||||
|
}
|
||||||
|
|
||||||
func (c *CLI) ClientKey(args []string) error {
|
func (c *CLI) ClientKey(args []string) error {
|
||||||
flagSet := flag.NewFlagSet("clientkey", flag.ExitOnError)
|
flagSet := flag.NewFlagSet("clientkey", flag.ExitOnError)
|
||||||
filepath := flagSet.String("path", files.OpenVPNClientKeyPath, "file path to the client.key file")
|
filepath := flagSet.String("path", constants.ClientKey, "file path to the client.key file")
|
||||||
if err := flagSet.Parse(args); err != nil {
|
if err := flagSet.Parse(args); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,49 +6,51 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
|
||||||
"github.com/qdm12/gluetun/internal/storage"
|
"github.com/qdm12/gluetun/internal/storage"
|
||||||
"golang.org/x/text/cases"
|
|
||||||
"golang.org/x/text/language"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type ServersFormatter interface {
|
||||||
|
FormatServers(args []string) error
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrFormatNotRecognized = errors.New("format is not recognized")
|
ErrFormatNotRecognized = errors.New("format is not recognized")
|
||||||
ErrProviderUnspecified = errors.New("VPN provider to format was not specified")
|
ErrProviderUnspecified = errors.New("VPN provider to format was not specified")
|
||||||
ErrMultipleProvidersToFormat = errors.New("more than one VPN provider to format were specified")
|
ErrOpenOutputFile = errors.New("cannot open output file")
|
||||||
|
ErrWriteOutput = errors.New("cannot write to output file")
|
||||||
|
ErrCloseOutputFile = errors.New("cannot close output file")
|
||||||
)
|
)
|
||||||
|
|
||||||
func addProviderFlag(flagSet *flag.FlagSet, providerToFormat map[string]*bool,
|
|
||||||
provider string, titleCaser cases.Caser) {
|
|
||||||
boolPtr, ok := providerToFormat[provider]
|
|
||||||
if !ok {
|
|
||||||
panic(fmt.Sprintf("unknown provider in format map: %s", provider))
|
|
||||||
}
|
|
||||||
flagSet.BoolVar(boolPtr, provider, false, "Format "+titleCaser.String(provider)+" servers")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CLI) FormatServers(args []string) error {
|
func (c *CLI) FormatServers(args []string) error {
|
||||||
var format, output string
|
var format, output string
|
||||||
allProviders := providers.All()
|
var cyberghost, expressvpn, fastestvpn, hideMyAss, ipvanish, ivpn, mullvad,
|
||||||
allProviderFlags := make([]string, len(allProviders))
|
nordvpn, perfectPrivacy, pia, privado, privatevpn, protonvpn, purevpn, surfshark,
|
||||||
for i, provider := range allProviders {
|
torguard, vpnUnlimited, vyprvpn, wevpn, windscribe bool
|
||||||
allProviderFlags[i] = strings.ReplaceAll(provider, " ", "-")
|
flagSet := flag.NewFlagSet("markdown", flag.ExitOnError)
|
||||||
}
|
|
||||||
|
|
||||||
providersToFormat := make(map[string]*bool, len(allProviders))
|
|
||||||
for _, provider := range allProviderFlags {
|
|
||||||
providersToFormat[provider] = new(bool)
|
|
||||||
}
|
|
||||||
flagSet := flag.NewFlagSet("format-servers", flag.ExitOnError)
|
|
||||||
flagSet.StringVar(&format, "format", "markdown", "Format to use which can be: 'markdown'")
|
flagSet.StringVar(&format, "format", "markdown", "Format to use which can be: 'markdown'")
|
||||||
flagSet.StringVar(&output, "output", "/dev/stdout", "Output file to write the formatted data to")
|
flagSet.StringVar(&output, "output", "/dev/stdout", "Output file to write the formatted data to")
|
||||||
titleCaser := cases.Title(language.English)
|
flagSet.BoolVar(&cyberghost, "cyberghost", false, "Format Cyberghost servers")
|
||||||
for _, provider := range allProviderFlags {
|
flagSet.BoolVar(&expressvpn, "expressvpn", false, "Format ExpressVPN servers")
|
||||||
addProviderFlag(flagSet, providersToFormat, provider, titleCaser)
|
flagSet.BoolVar(&fastestvpn, "fastestvpn", false, "Format FastestVPN servers")
|
||||||
}
|
flagSet.BoolVar(&hideMyAss, "hidemyass", false, "Format HideMyAss servers")
|
||||||
|
flagSet.BoolVar(&ipvanish, "ipvanish", false, "Format IpVanish servers")
|
||||||
|
flagSet.BoolVar(&ivpn, "ivpn", false, "Format IVPN servers")
|
||||||
|
flagSet.BoolVar(&mullvad, "mullvad", false, "Format Mullvad servers")
|
||||||
|
flagSet.BoolVar(&nordvpn, "nordvpn", false, "Format Nordvpn servers")
|
||||||
|
flagSet.BoolVar(&perfectPrivacy, "perfectprivacy", false, "Format Perfect Privacy servers")
|
||||||
|
flagSet.BoolVar(&pia, "pia", false, "Format Private Internet Access servers")
|
||||||
|
flagSet.BoolVar(&privado, "privado", false, "Format Privado servers")
|
||||||
|
flagSet.BoolVar(&privatevpn, "privatevpn", false, "Format Private VPN servers")
|
||||||
|
flagSet.BoolVar(&protonvpn, "protonvpn", false, "Format Protonvpn servers")
|
||||||
|
flagSet.BoolVar(&purevpn, "purevpn", false, "Format Purevpn servers")
|
||||||
|
flagSet.BoolVar(&surfshark, "surfshark", false, "Format Surfshark servers")
|
||||||
|
flagSet.BoolVar(&torguard, "torguard", false, "Format Torguard servers")
|
||||||
|
flagSet.BoolVar(&vpnUnlimited, "vpnunlimited", false, "Format VPN Unlimited servers")
|
||||||
|
flagSet.BoolVar(&vyprvpn, "vyprvpn", false, "Format Vyprvpn servers")
|
||||||
|
flagSet.BoolVar(&wevpn, "wevpn", false, "Format WeVPN servers")
|
||||||
|
flagSet.BoolVar(&windscribe, "windscribe", false, "Format Windscribe servers")
|
||||||
if err := flagSet.Parse(args); err != nil {
|
if err := flagSet.Parse(args); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -57,53 +59,74 @@ func (c *CLI) FormatServers(args []string) error {
|
|||||||
return fmt.Errorf("%w: %s", ErrFormatNotRecognized, format)
|
return fmt.Errorf("%w: %s", ErrFormatNotRecognized, format)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify only one provider is set to be formatted.
|
|
||||||
var providers []string
|
|
||||||
for provider, formatPtr := range providersToFormat {
|
|
||||||
if *formatPtr {
|
|
||||||
providers = append(providers, provider)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
switch len(providers) {
|
|
||||||
case 0:
|
|
||||||
return fmt.Errorf("%w", ErrProviderUnspecified)
|
|
||||||
case 1:
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("%w: %d specified: %s",
|
|
||||||
ErrMultipleProvidersToFormat, len(providers),
|
|
||||||
strings.Join(providers, ", "))
|
|
||||||
}
|
|
||||||
|
|
||||||
var providerToFormat string
|
|
||||||
for _, providerToFormat = range allProviders {
|
|
||||||
if strings.ReplaceAll(providerToFormat, " ", "-") == providers[0] {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
logger := newNoopLogger()
|
logger := newNoopLogger()
|
||||||
storage, err := storage.New(logger, constants.ServersData)
|
storage, err := storage.New(logger, constants.ServersData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("creating servers storage: %w", err)
|
return fmt.Errorf("%w: %s", ErrNewStorage, err)
|
||||||
}
|
}
|
||||||
|
currentServers := storage.GetServers()
|
||||||
|
|
||||||
formatted := storage.FormatToMarkdown(providerToFormat)
|
var formatted string
|
||||||
|
switch {
|
||||||
|
case cyberghost:
|
||||||
|
formatted = currentServers.Cyberghost.ToMarkdown()
|
||||||
|
case expressvpn:
|
||||||
|
formatted = currentServers.Expressvpn.ToMarkdown()
|
||||||
|
case fastestvpn:
|
||||||
|
formatted = currentServers.Fastestvpn.ToMarkdown()
|
||||||
|
case hideMyAss:
|
||||||
|
formatted = currentServers.HideMyAss.ToMarkdown()
|
||||||
|
case ipvanish:
|
||||||
|
formatted = currentServers.Ipvanish.ToMarkdown()
|
||||||
|
case ivpn:
|
||||||
|
formatted = currentServers.Ivpn.ToMarkdown()
|
||||||
|
case mullvad:
|
||||||
|
formatted = currentServers.Mullvad.ToMarkdown()
|
||||||
|
case nordvpn:
|
||||||
|
formatted = currentServers.Nordvpn.ToMarkdown()
|
||||||
|
case perfectPrivacy:
|
||||||
|
formatted = currentServers.Perfectprivacy.ToMarkdown()
|
||||||
|
case pia:
|
||||||
|
formatted = currentServers.Pia.ToMarkdown()
|
||||||
|
case privado:
|
||||||
|
formatted = currentServers.Privado.ToMarkdown()
|
||||||
|
case privatevpn:
|
||||||
|
formatted = currentServers.Privatevpn.ToMarkdown()
|
||||||
|
case protonvpn:
|
||||||
|
formatted = currentServers.Protonvpn.ToMarkdown()
|
||||||
|
case purevpn:
|
||||||
|
formatted = currentServers.Purevpn.ToMarkdown()
|
||||||
|
case surfshark:
|
||||||
|
formatted = currentServers.Surfshark.ToMarkdown()
|
||||||
|
case torguard:
|
||||||
|
formatted = currentServers.Torguard.ToMarkdown()
|
||||||
|
case vpnUnlimited:
|
||||||
|
formatted = currentServers.VPNUnlimited.ToMarkdown()
|
||||||
|
case vyprvpn:
|
||||||
|
formatted = currentServers.Vyprvpn.ToMarkdown()
|
||||||
|
case wevpn:
|
||||||
|
formatted = currentServers.Wevpn.ToMarkdown()
|
||||||
|
case windscribe:
|
||||||
|
formatted = currentServers.Windscribe.ToMarkdown()
|
||||||
|
default:
|
||||||
|
return ErrProviderUnspecified
|
||||||
|
}
|
||||||
|
|
||||||
output = filepath.Clean(output)
|
output = filepath.Clean(output)
|
||||||
file, err := os.OpenFile(output, os.O_TRUNC|os.O_WRONLY|os.O_CREATE, 0644)
|
file, err := os.OpenFile(output, os.O_TRUNC|os.O_WRONLY|os.O_CREATE, 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("opening output file: %w", err)
|
return fmt.Errorf("%w: %s", ErrOpenOutputFile, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = fmt.Fprint(file, formatted)
|
_, err = fmt.Fprint(file, formatted)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = file.Close()
|
_ = file.Close()
|
||||||
return fmt.Errorf("writing to output file: %w", err)
|
return fmt.Errorf("%w: %s", ErrWriteOutput, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = file.Close()
|
err = file.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("closing output file: %w", err)
|
return fmt.Errorf("%w: %s", ErrCloseOutputFile, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -6,18 +6,21 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration/sources"
|
||||||
"github.com/qdm12/gluetun/internal/healthcheck"
|
"github.com/qdm12/gluetun/internal/healthcheck"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c *CLI) HealthCheck(ctx context.Context, source Source, _ Warner) error {
|
type HealthChecker interface {
|
||||||
|
HealthCheck(ctx context.Context, source sources.Source, warner Warner) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CLI) HealthCheck(ctx context.Context, source sources.Source, warner Warner) error {
|
||||||
// Extract the health server port from the configuration.
|
// Extract the health server port from the configuration.
|
||||||
config, err := source.ReadHealth()
|
config, err := source.ReadHealth()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
config.SetDefaults()
|
|
||||||
|
|
||||||
err = config.Validate()
|
err = config.Validate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ func newNoopLogger() *noopLogger {
|
|||||||
return new(noopLogger)
|
return new(noopLogger)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *noopLogger) Debug(string) {}
|
func (l *noopLogger) Debug(s string) {}
|
||||||
func (l *noopLogger) Info(string) {}
|
func (l *noopLogger) Info(s string) {}
|
||||||
func (l *noopLogger) Warn(string) {}
|
func (l *noopLogger) Warn(s string) {}
|
||||||
func (l *noopLogger) Error(string) {}
|
func (l *noopLogger) Error(s string) {}
|
||||||
func (l *noopLogger) PatchLevel(logging.Level) {}
|
func (l *noopLogger) PatchLevel(level logging.Level) {}
|
||||||
func (l *noopLogger) PatchPrefix(string) {}
|
func (l *noopLogger) PatchPrefix(prefix string) {}
|
||||||
|
|||||||
@@ -1,85 +1,51 @@
|
|||||||
package cli
|
package cli
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
|
||||||
"net/netip"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration/sources"
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
|
||||||
"github.com/qdm12/gluetun/internal/openvpn/extract"
|
|
||||||
"github.com/qdm12/gluetun/internal/provider"
|
"github.com/qdm12/gluetun/internal/provider"
|
||||||
"github.com/qdm12/gluetun/internal/storage"
|
"github.com/qdm12/gluetun/internal/storage"
|
||||||
"github.com/qdm12/gluetun/internal/updater/resolver"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type OpenvpnConfigMaker interface {
|
||||||
|
OpenvpnConfig(logger OpenvpnConfigLogger, source sources.Source) error
|
||||||
|
}
|
||||||
|
|
||||||
type OpenvpnConfigLogger interface {
|
type OpenvpnConfigLogger interface {
|
||||||
Info(s string)
|
Info(s string)
|
||||||
Warn(s string)
|
Warn(s string)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Unzipper interface {
|
func (c *CLI) OpenvpnConfig(logger OpenvpnConfigLogger, source sources.Source) error {
|
||||||
FetchAndExtract(ctx context.Context, url string) (
|
|
||||||
contents map[string][]byte, err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type ParallelResolver interface {
|
|
||||||
Resolve(ctx context.Context, settings resolver.ParallelSettings) (
|
|
||||||
hostToIPs map[string][]netip.Addr, warnings []string, err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type IPFetcher interface {
|
|
||||||
FetchInfo(ctx context.Context, ip netip.Addr) (data models.PublicIP, err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type IPv6Checker interface {
|
|
||||||
IsIPv6Supported() (supported bool, err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CLI) OpenvpnConfig(logger OpenvpnConfigLogger, source Source,
|
|
||||||
ipv6Checker IPv6Checker) error {
|
|
||||||
storage, err := storage.New(logger, constants.ServersData)
|
storage, err := storage.New(logger, constants.ServersData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
allServers := storage.GetServers()
|
||||||
|
|
||||||
allSettings, err := source.Read()
|
allSettings, err := source.Read()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
ipv6Supported, err := ipv6Checker.IsIPv6Supported()
|
if err = allSettings.Validate(allServers); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
providerConf := provider.New(*allSettings.VPN.Provider.Name, allServers, time.Now)
|
||||||
|
connection, err := providerConf.GetConnection(allSettings.VPN.Provider.ServerSelection)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("checking for IPv6 support: %w", err)
|
return err
|
||||||
}
|
}
|
||||||
|
lines, err := providerConf.BuildConf(connection, allSettings.VPN.OpenVPN)
|
||||||
if err = allSettings.Validate(storage, ipv6Supported); err != nil {
|
|
||||||
return fmt.Errorf("validating settings: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unused by this CLI command
|
|
||||||
unzipper := (Unzipper)(nil)
|
|
||||||
client := (*http.Client)(nil)
|
|
||||||
warner := (Warner)(nil)
|
|
||||||
parallelResolver := (ParallelResolver)(nil)
|
|
||||||
ipFetcher := (IPFetcher)(nil)
|
|
||||||
openvpnFileExtractor := extract.New()
|
|
||||||
|
|
||||||
providers := provider.NewProviders(storage, time.Now, warner, client,
|
|
||||||
unzipper, parallelResolver, ipFetcher, openvpnFileExtractor)
|
|
||||||
providerConf := providers.Get(*allSettings.VPN.Provider.Name)
|
|
||||||
connection, err := providerConf.GetConnection(
|
|
||||||
allSettings.VPN.Provider.ServerSelection, ipv6Supported)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
lines := providerConf.OpenVPNConfig(connection,
|
|
||||||
allSettings.VPN.OpenVPN, ipv6Supported)
|
|
||||||
|
|
||||||
fmt.Println(strings.Join(lines, "\n"))
|
fmt.Println(strings.Join(lines, "\n"))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,105 +2,131 @@ package cli
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
"github.com/qdm12/gluetun/internal/openvpn/extract"
|
|
||||||
"github.com/qdm12/gluetun/internal/provider"
|
|
||||||
"github.com/qdm12/gluetun/internal/publicip/api"
|
|
||||||
"github.com/qdm12/gluetun/internal/storage"
|
"github.com/qdm12/gluetun/internal/storage"
|
||||||
"github.com/qdm12/gluetun/internal/updater"
|
"github.com/qdm12/gluetun/internal/updater"
|
||||||
"github.com/qdm12/gluetun/internal/updater/resolver"
|
|
||||||
"github.com/qdm12/gluetun/internal/updater/unzip"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrModeUnspecified = errors.New("at least one of -enduser or -maintainer must be specified")
|
ErrModeUnspecified = errors.New("at least one of -enduser or -maintainer must be specified")
|
||||||
ErrNoProviderSpecified = errors.New("no provider was specified")
|
ErrDNSAddress = errors.New("DNS address is not valid")
|
||||||
|
ErrNoProviderSpecified = errors.New("no provider was specified")
|
||||||
|
ErrNewStorage = errors.New("cannot create storage")
|
||||||
|
ErrUpdateServerInformation = errors.New("cannot update server information")
|
||||||
|
ErrWriteToFile = errors.New("cannot write updated information to file")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Updater interface {
|
||||||
|
Update(ctx context.Context, args []string, logger UpdaterLogger) error
|
||||||
|
}
|
||||||
|
|
||||||
type UpdaterLogger interface {
|
type UpdaterLogger interface {
|
||||||
Info(s string)
|
Info(s string)
|
||||||
Warn(s string)
|
Warn(s string)
|
||||||
Error(s string)
|
Error(s string)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func boolPtr(b bool) *bool { return &b }
|
||||||
|
|
||||||
func (c *CLI) Update(ctx context.Context, args []string, logger UpdaterLogger) error {
|
func (c *CLI) Update(ctx context.Context, args []string, logger UpdaterLogger) error {
|
||||||
options := settings.Updater{}
|
options := settings.Updater{CLI: boolPtr(true)}
|
||||||
var endUserMode, maintainerMode, updateAll bool
|
var endUserMode, maintainerMode, updateAll bool
|
||||||
var csvProviders, ipToken string
|
var dnsAddress, csvProviders string
|
||||||
flagSet := flag.NewFlagSet("update", flag.ExitOnError)
|
flagSet := flag.NewFlagSet("update", flag.ExitOnError)
|
||||||
flagSet.BoolVar(&endUserMode, "enduser", false, "Write results to /gluetun/servers.json (for end users)")
|
flagSet.BoolVar(&endUserMode, "enduser", false, "Write results to /gluetun/servers.json (for end users)")
|
||||||
flagSet.BoolVar(&maintainerMode, "maintainer", false,
|
flagSet.BoolVar(&maintainerMode, "maintainer", false,
|
||||||
"Write results to ./internal/storage/servers.json to modify the program (for maintainers)")
|
"Write results to ./internal/storage/servers.json to modify the program (for maintainers)")
|
||||||
flagSet.StringVar(&options.DNSAddress, "dns", "8.8.8.8", "DNS resolver address to use")
|
flagSet.StringVar(&dnsAddress, "dns", "8.8.8.8", "DNS resolver address to use")
|
||||||
const defaultMinRatio = 0.8
|
|
||||||
flagSet.Float64Var(&options.MinRatio, "minratio", defaultMinRatio,
|
|
||||||
"Minimum ratio of servers to find for the update to succeed")
|
|
||||||
flagSet.BoolVar(&updateAll, "all", false, "Update servers for all VPN providers")
|
flagSet.BoolVar(&updateAll, "all", false, "Update servers for all VPN providers")
|
||||||
flagSet.StringVar(&csvProviders, "providers", "", "CSV string of VPN providers to update server data for")
|
flagSet.StringVar(&csvProviders, "providers", "", "CSV string of VPN providers to update server data for")
|
||||||
flagSet.StringVar(&ipToken, "ip-token", "", "IP data service token (e.g. ipinfo.io) to use")
|
|
||||||
if err := flagSet.Parse(args); err != nil {
|
if err := flagSet.Parse(args); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !endUserMode && !maintainerMode {
|
if !endUserMode && !maintainerMode {
|
||||||
return fmt.Errorf("%w", ErrModeUnspecified)
|
return ErrModeUnspecified
|
||||||
|
}
|
||||||
|
|
||||||
|
options.DNSAddress = net.ParseIP(dnsAddress)
|
||||||
|
if options.DNSAddress == nil {
|
||||||
|
return fmt.Errorf("%w: %s", ErrDNSAddress, dnsAddress)
|
||||||
}
|
}
|
||||||
|
|
||||||
if updateAll {
|
if updateAll {
|
||||||
options.Providers = providers.All()
|
for _, provider := range constants.AllProviders() {
|
||||||
|
if provider == constants.Custom {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
options.Providers = append(options.Providers, provider)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if csvProviders == "" {
|
if csvProviders == "" {
|
||||||
return fmt.Errorf("%w", ErrNoProviderSpecified)
|
return ErrNoProviderSpecified
|
||||||
}
|
}
|
||||||
options.Providers = strings.Split(csvProviders, ",")
|
options.Providers = strings.Split(csvProviders, ",")
|
||||||
}
|
}
|
||||||
|
|
||||||
options.SetDefaults(options.Providers[0])
|
options.SetDefaults()
|
||||||
|
|
||||||
err := options.Validate()
|
err := options.Validate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("options validation failed: %w", err)
|
return fmt.Errorf("options validation failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
storage, err := storage.New(logger, constants.ServersData)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("creating servers storage: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
const clientTimeout = 10 * time.Second
|
const clientTimeout = 10 * time.Second
|
||||||
httpClient := &http.Client{Timeout: clientTimeout}
|
httpClient := &http.Client{Timeout: clientTimeout}
|
||||||
unzipper := unzip.New(httpClient)
|
|
||||||
parallelResolver := resolver.NewParallelResolver(options.DNSAddress)
|
storage, err := storage.New(logger, constants.ServersData)
|
||||||
ipFetcher, err := api.New(api.IPInfo, httpClient, ipToken)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("creating public IP API client: %w", err)
|
return fmt.Errorf("%w: %s", ErrNewStorage, err)
|
||||||
}
|
}
|
||||||
openvpnFileExtractor := extract.New()
|
currentServers := storage.GetServers()
|
||||||
|
|
||||||
providers := provider.NewProviders(storage, time.Now, logger, httpClient,
|
updater := updater.New(options, httpClient, currentServers, logger)
|
||||||
unzipper, parallelResolver, ipFetcher, openvpnFileExtractor)
|
allServers, err := updater.UpdateServers(ctx)
|
||||||
|
|
||||||
updater := updater.New(httpClient, storage, providers, logger)
|
|
||||||
err = updater.UpdateServers(ctx, options.Providers, options.MinRatio)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("updating server information: %w", err)
|
return fmt.Errorf("%w: %s", ErrUpdateServerInformation, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if endUserMode {
|
||||||
|
if err := storage.FlushToFile(allServers); err != nil {
|
||||||
|
return fmt.Errorf("%w: %s", ErrWriteToFile, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if maintainerMode {
|
if maintainerMode {
|
||||||
err := storage.FlushToFile(c.repoServersPath)
|
if err := writeToEmbeddedJSON(c.repoServersPath, allServers); err != nil {
|
||||||
if err != nil {
|
return fmt.Errorf("%w: %s", ErrWriteToFile, err)
|
||||||
return fmt.Errorf("writing servers data to embedded JSON file: %w", err)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func writeToEmbeddedJSON(repoServersPath string,
|
||||||
|
allServers models.AllServers) error {
|
||||||
|
const perms = 0600
|
||||||
|
f, err := os.OpenFile(repoServersPath,
|
||||||
|
os.O_TRUNC|os.O_WRONLY|os.O_CREATE, perms)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
encoder := json.NewEncoder(f)
|
||||||
|
encoder.SetIndent("", " ")
|
||||||
|
return encoder.Encode(allServers)
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ package settings
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/netip"
|
"net"
|
||||||
|
|
||||||
"github.com/qdm12/gosettings"
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||||
"github.com/qdm12/gotree"
|
"github.com/qdm12/gotree"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -13,17 +13,13 @@ type DNS struct {
|
|||||||
// ServerAddress is the DNS server to use inside
|
// ServerAddress is the DNS server to use inside
|
||||||
// the Go program and for the system.
|
// the Go program and for the system.
|
||||||
// It defaults to '127.0.0.1' to be used with the
|
// It defaults to '127.0.0.1' to be used with the
|
||||||
// DoT server. It cannot be the zero value in the internal
|
// DoT server. It cannot be nil in the internal
|
||||||
// state.
|
// state.
|
||||||
ServerAddress netip.Addr
|
ServerAddress net.IP
|
||||||
// KeepNameserver is true if the existing DNS server
|
// KeepNameserver is true if the Docker DNS server
|
||||||
// found in /etc/resolv.conf should be used
|
// found in /etc/resolv.conf should be kept.
|
||||||
// Note setting this to true will likely DNS traffic
|
// Note settings this to true will go around the
|
||||||
// outside the VPN tunnel since it would go through
|
// DoT server blocking.
|
||||||
// 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
|
// It defaults to false and cannot be nil in the
|
||||||
// internal state.
|
// internal state.
|
||||||
KeepNameserver *bool
|
KeepNameserver *bool
|
||||||
@@ -35,7 +31,7 @@ type DNS struct {
|
|||||||
func (d DNS) validate() (err error) {
|
func (d DNS) validate() (err error) {
|
||||||
err = d.DoT.validate()
|
err = d.DoT.validate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("validating DoT settings: %w", err)
|
return fmt.Errorf("failed validating DoT settings: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -43,8 +39,8 @@ func (d DNS) validate() (err error) {
|
|||||||
|
|
||||||
func (d *DNS) Copy() (copied DNS) {
|
func (d *DNS) Copy() (copied DNS) {
|
||||||
return DNS{
|
return DNS{
|
||||||
ServerAddress: d.ServerAddress,
|
ServerAddress: helpers.CopyIP(d.ServerAddress),
|
||||||
KeepNameserver: gosettings.CopyPointer(d.KeepNameserver),
|
KeepNameserver: helpers.CopyBoolPtr(d.KeepNameserver),
|
||||||
DoT: d.DoT.copy(),
|
DoT: d.DoT.copy(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -52,8 +48,8 @@ func (d *DNS) Copy() (copied DNS) {
|
|||||||
// mergeWith merges the other settings into any
|
// mergeWith merges the other settings into any
|
||||||
// unset field of the receiver settings object.
|
// unset field of the receiver settings object.
|
||||||
func (d *DNS) mergeWith(other DNS) {
|
func (d *DNS) mergeWith(other DNS) {
|
||||||
d.ServerAddress = gosettings.MergeWithValidator(d.ServerAddress, other.ServerAddress)
|
d.ServerAddress = helpers.MergeWithIP(d.ServerAddress, other.ServerAddress)
|
||||||
d.KeepNameserver = gosettings.MergeWithPointer(d.KeepNameserver, other.KeepNameserver)
|
d.KeepNameserver = helpers.MergeWithBool(d.KeepNameserver, other.KeepNameserver)
|
||||||
d.DoT.mergeWith(other.DoT)
|
d.DoT.mergeWith(other.DoT)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,15 +57,15 @@ func (d *DNS) mergeWith(other DNS) {
|
|||||||
// settings object with any field set in the other
|
// settings object with any field set in the other
|
||||||
// settings.
|
// settings.
|
||||||
func (d *DNS) overrideWith(other DNS) {
|
func (d *DNS) overrideWith(other DNS) {
|
||||||
d.ServerAddress = gosettings.OverrideWithValidator(d.ServerAddress, other.ServerAddress)
|
d.ServerAddress = helpers.OverrideWithIP(d.ServerAddress, other.ServerAddress)
|
||||||
d.KeepNameserver = gosettings.OverrideWithPointer(d.KeepNameserver, other.KeepNameserver)
|
d.KeepNameserver = helpers.OverrideWithBool(d.KeepNameserver, other.KeepNameserver)
|
||||||
d.DoT.overrideWith(other.DoT)
|
d.DoT.overrideWith(other.DoT)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DNS) setDefaults() {
|
func (d *DNS) setDefaults() {
|
||||||
localhost := netip.AddrFrom4([4]byte{127, 0, 0, 1})
|
localhost := net.IPv4(127, 0, 0, 1) //nolint:gomnd
|
||||||
d.ServerAddress = gosettings.DefaultValidator(d.ServerAddress, localhost)
|
d.ServerAddress = helpers.DefaultIP(d.ServerAddress, localhost)
|
||||||
d.KeepNameserver = gosettings.DefaultPointer(d.KeepNameserver, false)
|
d.KeepNameserver = helpers.DefaultBool(d.KeepNameserver, false)
|
||||||
d.DoT.setDefaults()
|
d.DoT.setDefaults()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,11 +75,8 @@ func (d DNS) String() string {
|
|||||||
|
|
||||||
func (d DNS) toLinesNode() (node *gotree.Node) {
|
func (d DNS) toLinesNode() (node *gotree.Node) {
|
||||||
node = gotree.New("DNS settings:")
|
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("DNS server address to use: %s", d.ServerAddress)
|
||||||
|
node.Appendf("Keep existing nameserver(s): %s", helpers.BoolPtrToYesNo(d.KeepNameserver))
|
||||||
node.AppendNode(d.DoT.toLinesNode())
|
node.AppendNode(d.DoT.toLinesNode())
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,12 +3,12 @@ package settings
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/netip"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
|
|
||||||
"github.com/qdm12/dns/pkg/blacklist"
|
"github.com/qdm12/dns/pkg/blacklist"
|
||||||
"github.com/qdm12/gosettings"
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||||
"github.com/qdm12/gotree"
|
"github.com/qdm12/gotree"
|
||||||
|
"inet.af/netaddr"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DNSBlacklist is settings for the DNS blacklist building.
|
// DNSBlacklist is settings for the DNS blacklist building.
|
||||||
@@ -18,14 +18,14 @@ type DNSBlacklist struct {
|
|||||||
BlockSurveillance *bool
|
BlockSurveillance *bool
|
||||||
AllowedHosts []string
|
AllowedHosts []string
|
||||||
AddBlockedHosts []string
|
AddBlockedHosts []string
|
||||||
AddBlockedIPs []netip.Addr
|
AddBlockedIPs []netaddr.IP
|
||||||
AddBlockedIPPrefixes []netip.Prefix
|
AddBlockedIPPrefixes []netaddr.IPPrefix
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *DNSBlacklist) setDefaults() {
|
func (b *DNSBlacklist) setDefaults() {
|
||||||
b.BlockMalicious = gosettings.DefaultPointer(b.BlockMalicious, true)
|
b.BlockMalicious = helpers.DefaultBool(b.BlockMalicious, true)
|
||||||
b.BlockAds = gosettings.DefaultPointer(b.BlockAds, false)
|
b.BlockAds = helpers.DefaultBool(b.BlockAds, false)
|
||||||
b.BlockSurveillance = gosettings.DefaultPointer(b.BlockSurveillance, true)
|
b.BlockSurveillance = helpers.DefaultBool(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
|
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) {
|
func (b DNSBlacklist) copy() (copied DNSBlacklist) {
|
||||||
return DNSBlacklist{
|
return DNSBlacklist{
|
||||||
BlockMalicious: gosettings.CopyPointer(b.BlockMalicious),
|
BlockMalicious: helpers.CopyBoolPtr(b.BlockMalicious),
|
||||||
BlockAds: gosettings.CopyPointer(b.BlockAds),
|
BlockAds: helpers.CopyBoolPtr(b.BlockAds),
|
||||||
BlockSurveillance: gosettings.CopyPointer(b.BlockSurveillance),
|
BlockSurveillance: helpers.CopyBoolPtr(b.BlockSurveillance),
|
||||||
AllowedHosts: gosettings.CopySlice(b.AllowedHosts),
|
AllowedHosts: helpers.CopyStringSlice(b.AllowedHosts),
|
||||||
AddBlockedHosts: gosettings.CopySlice(b.AddBlockedHosts),
|
AddBlockedHosts: helpers.CopyStringSlice(b.AddBlockedHosts),
|
||||||
AddBlockedIPs: gosettings.CopySlice(b.AddBlockedIPs),
|
AddBlockedIPs: helpers.CopyNetaddrIPsSlice(b.AddBlockedIPs),
|
||||||
AddBlockedIPPrefixes: gosettings.CopySlice(b.AddBlockedIPPrefixes),
|
AddBlockedIPPrefixes: helpers.CopyIPPrefixSlice(b.AddBlockedIPPrefixes),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *DNSBlacklist) mergeWith(other DNSBlacklist) {
|
func (b *DNSBlacklist) mergeWith(other DNSBlacklist) {
|
||||||
b.BlockMalicious = gosettings.MergeWithPointer(b.BlockMalicious, other.BlockMalicious)
|
b.BlockMalicious = helpers.MergeWithBool(b.BlockMalicious, other.BlockMalicious)
|
||||||
b.BlockAds = gosettings.MergeWithPointer(b.BlockAds, other.BlockAds)
|
b.BlockAds = helpers.MergeWithBool(b.BlockAds, other.BlockAds)
|
||||||
b.BlockSurveillance = gosettings.MergeWithPointer(b.BlockSurveillance, other.BlockSurveillance)
|
b.BlockSurveillance = helpers.MergeWithBool(b.BlockSurveillance, other.BlockSurveillance)
|
||||||
b.AllowedHosts = gosettings.MergeWithSlice(b.AllowedHosts, other.AllowedHosts)
|
b.AllowedHosts = helpers.MergeStringSlices(b.AllowedHosts, other.AllowedHosts)
|
||||||
b.AddBlockedHosts = gosettings.MergeWithSlice(b.AddBlockedHosts, other.AddBlockedHosts)
|
b.AddBlockedHosts = helpers.MergeStringSlices(b.AddBlockedHosts, other.AddBlockedHosts)
|
||||||
b.AddBlockedIPs = gosettings.MergeWithSlice(b.AddBlockedIPs, other.AddBlockedIPs)
|
b.AddBlockedIPs = helpers.MergeNetaddrIPsSlices(b.AddBlockedIPs, other.AddBlockedIPs)
|
||||||
b.AddBlockedIPPrefixes = gosettings.MergeWithSlice(b.AddBlockedIPPrefixes, other.AddBlockedIPPrefixes)
|
b.AddBlockedIPPrefixes = helpers.MergeIPPrefixesSlices(b.AddBlockedIPPrefixes, other.AddBlockedIPPrefixes)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *DNSBlacklist) overrideWith(other DNSBlacklist) {
|
func (b *DNSBlacklist) overrideWith(other DNSBlacklist) {
|
||||||
b.BlockMalicious = gosettings.OverrideWithPointer(b.BlockMalicious, other.BlockMalicious)
|
b.BlockMalicious = helpers.OverrideWithBool(b.BlockMalicious, other.BlockMalicious)
|
||||||
b.BlockAds = gosettings.OverrideWithPointer(b.BlockAds, other.BlockAds)
|
b.BlockAds = helpers.OverrideWithBool(b.BlockAds, other.BlockAds)
|
||||||
b.BlockSurveillance = gosettings.OverrideWithPointer(b.BlockSurveillance, other.BlockSurveillance)
|
b.BlockSurveillance = helpers.OverrideWithBool(b.BlockSurveillance, other.BlockSurveillance)
|
||||||
b.AllowedHosts = gosettings.OverrideWithSlice(b.AllowedHosts, other.AllowedHosts)
|
b.AllowedHosts = helpers.OverrideWithStringSlice(b.AllowedHosts, other.AllowedHosts)
|
||||||
b.AddBlockedHosts = gosettings.OverrideWithSlice(b.AddBlockedHosts, other.AddBlockedHosts)
|
b.AddBlockedHosts = helpers.OverrideWithStringSlice(b.AddBlockedHosts, other.AddBlockedHosts)
|
||||||
b.AddBlockedIPs = gosettings.OverrideWithSlice(b.AddBlockedIPs, other.AddBlockedIPs)
|
b.AddBlockedIPs = helpers.OverrideWithNetaddrIPsSlice(b.AddBlockedIPs, other.AddBlockedIPs)
|
||||||
b.AddBlockedIPPrefixes = gosettings.OverrideWithSlice(b.AddBlockedIPPrefixes, other.AddBlockedIPPrefixes)
|
b.AddBlockedIPPrefixes = helpers.OverrideWithIPPrefixesSlice(b.AddBlockedIPPrefixes, other.AddBlockedIPPrefixes)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b DNSBlacklist) ToBlacklistFormat() (settings blacklist.BuilderSettings, err error) {
|
func (b DNSBlacklist) ToBlacklistFormat() (settings blacklist.BuilderSettings, err error) {
|
||||||
@@ -90,8 +90,8 @@ func (b DNSBlacklist) ToBlacklistFormat() (settings blacklist.BuilderSettings, e
|
|||||||
BlockSurveillance: *b.BlockSurveillance,
|
BlockSurveillance: *b.BlockSurveillance,
|
||||||
AllowedHosts: b.AllowedHosts,
|
AllowedHosts: b.AllowedHosts,
|
||||||
AddBlockedHosts: b.AddBlockedHosts,
|
AddBlockedHosts: b.AddBlockedHosts,
|
||||||
AddBlockedIPs: netipAddressesToNetaddrIPs(b.AddBlockedIPs),
|
AddBlockedIPs: b.AddBlockedIPs,
|
||||||
AddBlockedIPPrefixes: netipPrefixesToNetaddrIPPrefixes(b.AddBlockedIPPrefixes),
|
AddBlockedIPPrefixes: b.AddBlockedIPPrefixes,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,9 +102,9 @@ func (b DNSBlacklist) String() string {
|
|||||||
func (b DNSBlacklist) toLinesNode() (node *gotree.Node) {
|
func (b DNSBlacklist) toLinesNode() (node *gotree.Node) {
|
||||||
node = gotree.New("DNS filtering settings:")
|
node = gotree.New("DNS filtering settings:")
|
||||||
|
|
||||||
node.Appendf("Block malicious: %s", gosettings.BoolToYesNo(b.BlockMalicious))
|
node.Appendf("Block malicious: %s", helpers.BoolPtrToYesNo(b.BlockMalicious))
|
||||||
node.Appendf("Block ads: %s", gosettings.BoolToYesNo(b.BlockAds))
|
node.Appendf("Block ads: %s", helpers.BoolPtrToYesNo(b.BlockAds))
|
||||||
node.Appendf("Block surveillance: %s", gosettings.BoolToYesNo(b.BlockSurveillance))
|
node.Appendf("Block surveillance: %s", helpers.BoolPtrToYesNo(b.BlockSurveillance))
|
||||||
|
|
||||||
if len(b.AllowedHosts) > 0 {
|
if len(b.AllowedHosts) > 0 {
|
||||||
allowedHostsNode := node.Appendf("Allowed hosts:")
|
allowedHostsNode := node.Appendf("Allowed hosts:")
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/qdm12/gosettings"
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||||
"github.com/qdm12/gotree"
|
"github.com/qdm12/gotree"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -54,8 +54,8 @@ func (d DoT) validate() (err error) {
|
|||||||
|
|
||||||
func (d *DoT) copy() (copied DoT) {
|
func (d *DoT) copy() (copied DoT) {
|
||||||
return DoT{
|
return DoT{
|
||||||
Enabled: gosettings.CopyPointer(d.Enabled),
|
Enabled: helpers.CopyBoolPtr(d.Enabled),
|
||||||
UpdatePeriod: gosettings.CopyPointer(d.UpdatePeriod),
|
UpdatePeriod: helpers.CopyDurationPtr(d.UpdatePeriod),
|
||||||
Unbound: d.Unbound.copy(),
|
Unbound: d.Unbound.copy(),
|
||||||
Blacklist: d.Blacklist.copy(),
|
Blacklist: d.Blacklist.copy(),
|
||||||
}
|
}
|
||||||
@@ -64,8 +64,8 @@ func (d *DoT) copy() (copied DoT) {
|
|||||||
// mergeWith merges the other settings into any
|
// mergeWith merges the other settings into any
|
||||||
// unset field of the receiver settings object.
|
// unset field of the receiver settings object.
|
||||||
func (d *DoT) mergeWith(other DoT) {
|
func (d *DoT) mergeWith(other DoT) {
|
||||||
d.Enabled = gosettings.MergeWithPointer(d.Enabled, other.Enabled)
|
d.Enabled = helpers.MergeWithBool(d.Enabled, other.Enabled)
|
||||||
d.UpdatePeriod = gosettings.MergeWithPointer(d.UpdatePeriod, other.UpdatePeriod)
|
d.UpdatePeriod = helpers.MergeWithDuration(d.UpdatePeriod, other.UpdatePeriod)
|
||||||
d.Unbound.mergeWith(other.Unbound)
|
d.Unbound.mergeWith(other.Unbound)
|
||||||
d.Blacklist.mergeWith(other.Blacklist)
|
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 object with any field set in the other
|
||||||
// settings.
|
// settings.
|
||||||
func (d *DoT) overrideWith(other DoT) {
|
func (d *DoT) overrideWith(other DoT) {
|
||||||
d.Enabled = gosettings.OverrideWithPointer(d.Enabled, other.Enabled)
|
d.Enabled = helpers.OverrideWithBool(d.Enabled, other.Enabled)
|
||||||
d.UpdatePeriod = gosettings.OverrideWithPointer(d.UpdatePeriod, other.UpdatePeriod)
|
d.UpdatePeriod = helpers.OverrideWithDuration(d.UpdatePeriod, other.UpdatePeriod)
|
||||||
d.Unbound.overrideWith(other.Unbound)
|
d.Unbound.overrideWith(other.Unbound)
|
||||||
d.Blacklist.overrideWith(other.Blacklist)
|
d.Blacklist.overrideWith(other.Blacklist)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DoT) setDefaults() {
|
func (d *DoT) setDefaults() {
|
||||||
d.Enabled = gosettings.DefaultPointer(d.Enabled, true)
|
d.Enabled = helpers.DefaultBool(d.Enabled, true)
|
||||||
const defaultUpdatePeriod = 24 * time.Hour
|
const defaultUpdatePeriod = 24 * time.Hour
|
||||||
d.UpdatePeriod = gosettings.DefaultPointer(d.UpdatePeriod, defaultUpdatePeriod)
|
d.UpdatePeriod = helpers.DefaultDuration(d.UpdatePeriod, defaultUpdatePeriod)
|
||||||
d.Unbound.setDefaults()
|
d.Unbound.setDefaults()
|
||||||
d.Blacklist.setDefaults()
|
d.Blacklist.setDefaults()
|
||||||
}
|
}
|
||||||
@@ -95,12 +95,12 @@ func (d DoT) String() string {
|
|||||||
func (d DoT) toLinesNode() (node *gotree.Node) {
|
func (d DoT) toLinesNode() (node *gotree.Node) {
|
||||||
node = gotree.New("DNS over TLS settings:")
|
node = gotree.New("DNS over TLS settings:")
|
||||||
|
|
||||||
node.Appendf("Enabled: %s", gosettings.BoolToYesNo(d.Enabled))
|
node.Appendf("Enabled: %s", helpers.BoolPtrToYesNo(d.Enabled))
|
||||||
if !*d.Enabled {
|
if !*d.Enabled {
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
|
|
||||||
update := "disabled" //nolint:goconst
|
update := "disabled"
|
||||||
if *d.UpdatePeriod > 0 {
|
if *d.UpdatePeriod > 0 {
|
||||||
update = "every " + d.UpdatePeriod.String()
|
update = "every " + d.UpdatePeriod.String()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,21 +5,19 @@ import "errors"
|
|||||||
var (
|
var (
|
||||||
ErrCityNotValid = errors.New("the city specified is not valid")
|
ErrCityNotValid = errors.New("the city specified is not valid")
|
||||||
ErrControlServerPrivilegedPort = errors.New("cannot use privileged port without running as root")
|
ErrControlServerPrivilegedPort = errors.New("cannot use privileged port without running as root")
|
||||||
ErrCategoryNotValid = errors.New("the category specified is not valid")
|
|
||||||
ErrCountryNotValid = errors.New("the country specified is not valid")
|
ErrCountryNotValid = errors.New("the country specified is not valid")
|
||||||
ErrFilepathMissing = errors.New("filepath is missing")
|
ErrFirewallZeroPort = errors.New("cannot have a zero port to block")
|
||||||
ErrFirewallZeroPort = errors.New("cannot have a zero port")
|
|
||||||
ErrFirewallPublicOutboundSubnet = errors.New("outbound subnet has an unspecified address")
|
|
||||||
ErrHostnameNotValid = errors.New("the hostname specified is not valid")
|
ErrHostnameNotValid = errors.New("the hostname specified is not valid")
|
||||||
ErrISPNotValid = errors.New("the ISP specified is not valid")
|
ErrISPNotValid = errors.New("the ISP specified is not valid")
|
||||||
ErrMinRatioNotValid = errors.New("minimum ratio is not valid")
|
|
||||||
ErrMissingValue = errors.New("missing value")
|
|
||||||
ErrNameNotValid = errors.New("the server name specified is not valid")
|
ErrNameNotValid = errors.New("the server name specified is not valid")
|
||||||
|
ErrOpenVPNClientCertMissing = errors.New("client certificate is missing")
|
||||||
|
ErrOpenVPNClientCertNotValid = errors.New("client certificate is not valid")
|
||||||
ErrOpenVPNClientKeyMissing = errors.New("client key is missing")
|
ErrOpenVPNClientKeyMissing = errors.New("client key is missing")
|
||||||
|
ErrOpenVPNClientKeyNotValid = errors.New("client key is not valid")
|
||||||
|
ErrOpenVPNConfigFile = errors.New("custom configuration file error")
|
||||||
ErrOpenVPNCustomPortNotAllowed = errors.New("custom endpoint port is not allowed")
|
ErrOpenVPNCustomPortNotAllowed = errors.New("custom endpoint port is not allowed")
|
||||||
ErrOpenVPNEncryptionPresetNotValid = errors.New("PIA encryption preset is not valid")
|
ErrOpenVPNEncryptionPresetNotValid = errors.New("PIA encryption preset is not valid")
|
||||||
ErrOpenVPNInterfaceNotValid = errors.New("interface name is not valid")
|
ErrOpenVPNInterfaceNotValid = errors.New("interface name is not valid")
|
||||||
ErrOpenVPNKeyPassphraseIsEmpty = errors.New("key passphrase is empty")
|
|
||||||
ErrOpenVPNMSSFixIsTooHigh = errors.New("mssfix option value is too high")
|
ErrOpenVPNMSSFixIsTooHigh = errors.New("mssfix option value is too high")
|
||||||
ErrOpenVPNPasswordIsEmpty = errors.New("password is empty")
|
ErrOpenVPNPasswordIsEmpty = errors.New("password is empty")
|
||||||
ErrOpenVPNTCPNotSupported = errors.New("TCP protocol is not supported")
|
ErrOpenVPNTCPNotSupported = errors.New("TCP protocol is not supported")
|
||||||
@@ -27,27 +25,27 @@ var (
|
|||||||
ErrOpenVPNVerbosityIsOutOfBounds = errors.New("verbosity value is out of bounds")
|
ErrOpenVPNVerbosityIsOutOfBounds = errors.New("verbosity value is out of bounds")
|
||||||
ErrOpenVPNVersionIsNotValid = errors.New("version is not valid")
|
ErrOpenVPNVersionIsNotValid = errors.New("version is not valid")
|
||||||
ErrPortForwardingEnabled = errors.New("port forwarding cannot be enabled")
|
ErrPortForwardingEnabled = errors.New("port forwarding cannot be enabled")
|
||||||
|
ErrPortForwardingFilepathNotValid = errors.New("port forwarding filepath given is not valid")
|
||||||
|
ErrPublicIPFilepathNotValid = errors.New("public IP address file path is not valid")
|
||||||
ErrPublicIPPeriodTooShort = errors.New("public IP address check period is too short")
|
ErrPublicIPPeriodTooShort = errors.New("public IP address check period is too short")
|
||||||
ErrRegionNotValid = errors.New("the region specified is not valid")
|
ErrRegionNotValid = errors.New("the region specified is not valid")
|
||||||
ErrServerAddressNotValid = errors.New("server listening address is not valid")
|
ErrServerAddressNotValid = errors.New("server listening address is not valid")
|
||||||
ErrSystemPGIDNotValid = errors.New("process group id is not valid")
|
ErrSystemPGIDNotValid = errors.New("process group id is not valid")
|
||||||
ErrSystemPUIDNotValid = errors.New("process user id is not valid")
|
ErrSystemPUIDNotValid = errors.New("process user id is not valid")
|
||||||
ErrSystemTimezoneNotValid = errors.New("timezone is not valid")
|
ErrSystemTimezoneNotValid = errors.New("timezone is not valid")
|
||||||
ErrUpdaterPeriodTooSmall = errors.New("VPN server data updater period is too small")
|
|
||||||
ErrVPNProviderNameNotValid = errors.New("VPN provider name is not valid")
|
ErrVPNProviderNameNotValid = errors.New("VPN provider name is not valid")
|
||||||
ErrVPNTypeNotValid = errors.New("VPN type is not valid")
|
ErrVPNTypeNotValid = errors.New("VPN type is not valid")
|
||||||
ErrWireguardAllowedIPNotSet = errors.New("allowed IP is not set")
|
|
||||||
ErrWireguardAllowedIPsNotSet = errors.New("allowed IPs is not set")
|
|
||||||
ErrWireguardEndpointIPNotSet = errors.New("endpoint IP is not set")
|
ErrWireguardEndpointIPNotSet = errors.New("endpoint IP is not set")
|
||||||
ErrWireguardEndpointPortNotAllowed = errors.New("endpoint port is not allowed")
|
ErrWireguardEndpointPortNotAllowed = errors.New("endpoint port is not allowed")
|
||||||
ErrWireguardEndpointPortNotSet = errors.New("endpoint port is not set")
|
ErrWireguardEndpointPortNotSet = errors.New("endpoint port is not set")
|
||||||
ErrWireguardEndpointPortSet = errors.New("endpoint port is set")
|
|
||||||
ErrWireguardInterfaceAddressNotSet = errors.New("interface address is not set")
|
ErrWireguardInterfaceAddressNotSet = errors.New("interface address is not set")
|
||||||
ErrWireguardInterfaceAddressIPv6 = errors.New("interface address is IPv6 but IPv6 is not supported")
|
|
||||||
ErrWireguardInterfaceNotValid = errors.New("interface name is not valid")
|
ErrWireguardInterfaceNotValid = errors.New("interface name is not valid")
|
||||||
ErrWireguardPreSharedKeyNotSet = errors.New("pre-shared key is not set")
|
ErrWireguardPreSharedKeyNotSet = errors.New("pre-shared key is not set")
|
||||||
|
ErrWireguardPreSharedKeyNotValid = errors.New("pre-shared key is not valid")
|
||||||
ErrWireguardPrivateKeyNotSet = errors.New("private key is not set")
|
ErrWireguardPrivateKeyNotSet = errors.New("private key is not set")
|
||||||
|
ErrWireguardPrivateKeyNotValid = errors.New("private key is not valid")
|
||||||
ErrWireguardPublicKeyNotSet = errors.New("public key is not set")
|
ErrWireguardPublicKeyNotSet = errors.New("public key is not set")
|
||||||
ErrWireguardPublicKeyNotValid = errors.New("public key is not valid")
|
ErrWireguardPublicKeyNotValid = errors.New("public key is not valid")
|
||||||
ErrWireguardImplementationNotValid = errors.New("implementation is not valid")
|
|
||||||
|
ErrUpdaterPeriodTooSmall = errors.New("VPN server data updater period is too small")
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ package settings
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/netip"
|
"net"
|
||||||
|
|
||||||
"github.com/qdm12/gosettings"
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||||
"github.com/qdm12/gotree"
|
"github.com/qdm12/gotree"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -12,7 +12,7 @@ import (
|
|||||||
type Firewall struct {
|
type Firewall struct {
|
||||||
VPNInputPorts []uint16
|
VPNInputPorts []uint16
|
||||||
InputPorts []uint16
|
InputPorts []uint16
|
||||||
OutboundSubnets []netip.Prefix
|
OutboundSubnets []net.IPNet
|
||||||
Enabled *bool
|
Enabled *bool
|
||||||
Debug *bool
|
Debug *bool
|
||||||
}
|
}
|
||||||
@@ -26,12 +26,6 @@ func (f Firewall) validate() (err error) {
|
|||||||
return fmt.Errorf("input ports: %w", ErrFirewallZeroPort)
|
return fmt.Errorf("input ports: %w", ErrFirewallZeroPort)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, subnet := range f.OutboundSubnets {
|
|
||||||
if subnet.Addr().IsUnspecified() {
|
|
||||||
return fmt.Errorf("%w: %s", ErrFirewallPublicOutboundSubnet, subnet)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,11 +40,11 @@ func hasZeroPort(ports []uint16) (has bool) {
|
|||||||
|
|
||||||
func (f *Firewall) copy() (copied Firewall) {
|
func (f *Firewall) copy() (copied Firewall) {
|
||||||
return Firewall{
|
return Firewall{
|
||||||
VPNInputPorts: gosettings.CopySlice(f.VPNInputPorts),
|
VPNInputPorts: helpers.CopyUint16Slice(f.VPNInputPorts),
|
||||||
InputPorts: gosettings.CopySlice(f.InputPorts),
|
InputPorts: helpers.CopyUint16Slice(f.InputPorts),
|
||||||
OutboundSubnets: gosettings.CopySlice(f.OutboundSubnets),
|
OutboundSubnets: helpers.CopyIPNetSlice(f.OutboundSubnets),
|
||||||
Enabled: gosettings.CopyPointer(f.Enabled),
|
Enabled: helpers.CopyBoolPtr(f.Enabled),
|
||||||
Debug: gosettings.CopyPointer(f.Debug),
|
Debug: helpers.CopyBoolPtr(f.Debug),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,27 +53,27 @@ func (f *Firewall) copy() (copied Firewall) {
|
|||||||
// It merges values of slices together, even if they
|
// It merges values of slices together, even if they
|
||||||
// are set in the receiver settings.
|
// are set in the receiver settings.
|
||||||
func (f *Firewall) mergeWith(other Firewall) {
|
func (f *Firewall) mergeWith(other Firewall) {
|
||||||
f.VPNInputPorts = gosettings.MergeWithSlice(f.VPNInputPorts, other.VPNInputPorts)
|
f.VPNInputPorts = helpers.MergeUint16Slices(f.VPNInputPorts, other.VPNInputPorts)
|
||||||
f.InputPorts = gosettings.MergeWithSlice(f.InputPorts, other.InputPorts)
|
f.InputPorts = helpers.MergeUint16Slices(f.InputPorts, other.InputPorts)
|
||||||
f.OutboundSubnets = gosettings.MergeWithSlice(f.OutboundSubnets, other.OutboundSubnets)
|
f.OutboundSubnets = helpers.MergeIPNetsSlices(f.OutboundSubnets, other.OutboundSubnets)
|
||||||
f.Enabled = gosettings.MergeWithPointer(f.Enabled, other.Enabled)
|
f.Enabled = helpers.MergeWithBool(f.Enabled, other.Enabled)
|
||||||
f.Debug = gosettings.MergeWithPointer(f.Debug, other.Debug)
|
f.Debug = helpers.MergeWithBool(f.Debug, other.Debug)
|
||||||
}
|
}
|
||||||
|
|
||||||
// overrideWith overrides fields of the receiver
|
// overrideWith overrides fields of the receiver
|
||||||
// settings object with any field set in the other
|
// settings object with any field set in the other
|
||||||
// settings.
|
// settings.
|
||||||
func (f *Firewall) overrideWith(other Firewall) {
|
func (f *Firewall) overrideWith(other Firewall) {
|
||||||
f.VPNInputPorts = gosettings.OverrideWithSlice(f.VPNInputPorts, other.VPNInputPorts)
|
f.VPNInputPorts = helpers.OverrideWithUint16Slice(f.VPNInputPorts, other.VPNInputPorts)
|
||||||
f.InputPorts = gosettings.OverrideWithSlice(f.InputPorts, other.InputPorts)
|
f.InputPorts = helpers.OverrideWithUint16Slice(f.InputPorts, other.InputPorts)
|
||||||
f.OutboundSubnets = gosettings.OverrideWithSlice(f.OutboundSubnets, other.OutboundSubnets)
|
f.OutboundSubnets = helpers.OverrideWithIPNetsSlice(f.OutboundSubnets, other.OutboundSubnets)
|
||||||
f.Enabled = gosettings.OverrideWithPointer(f.Enabled, other.Enabled)
|
f.Enabled = helpers.OverrideWithBool(f.Enabled, other.Enabled)
|
||||||
f.Debug = gosettings.OverrideWithPointer(f.Debug, other.Debug)
|
f.Debug = helpers.OverrideWithBool(f.Debug, other.Debug)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Firewall) setDefaults() {
|
func (f *Firewall) setDefaults() {
|
||||||
f.Enabled = gosettings.DefaultPointer(f.Enabled, true)
|
f.Enabled = helpers.DefaultBool(f.Enabled, true)
|
||||||
f.Debug = gosettings.DefaultPointer(f.Debug, false)
|
f.Debug = helpers.DefaultBool(f.Debug, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f Firewall) String() string {
|
func (f Firewall) String() string {
|
||||||
@@ -89,7 +83,7 @@ func (f Firewall) String() string {
|
|||||||
func (f Firewall) toLinesNode() (node *gotree.Node) {
|
func (f Firewall) toLinesNode() (node *gotree.Node) {
|
||||||
node = gotree.New("Firewall settings:")
|
node = gotree.New("Firewall settings:")
|
||||||
|
|
||||||
node.Appendf("Enabled: %s", gosettings.BoolToYesNo(f.Enabled))
|
node.Appendf("Enabled: %s", helpers.BoolPtrToYesNo(f.Enabled))
|
||||||
if !*f.Enabled {
|
if !*f.Enabled {
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
@@ -115,8 +109,7 @@ func (f Firewall) toLinesNode() (node *gotree.Node) {
|
|||||||
if len(f.OutboundSubnets) > 0 {
|
if len(f.OutboundSubnets) > 0 {
|
||||||
outboundSubnets := node.Appendf("Outbound subnets:")
|
outboundSubnets := node.Appendf("Outbound subnets:")
|
||||||
for _, subnet := range f.OutboundSubnets {
|
for _, subnet := range f.OutboundSubnets {
|
||||||
subnet := subnet
|
outboundSubnets.Appendf("%s", subnet)
|
||||||
outboundSubnets.Appendf("%s", &subnet)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,74 +0,0 @@
|
|||||||
package settings
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/netip"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Test_Firewall_validate(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
testCases := map[string]struct {
|
|
||||||
firewall Firewall
|
|
||||||
errWrapped error
|
|
||||||
errMessage string
|
|
||||||
}{
|
|
||||||
"empty": {},
|
|
||||||
"zero_vpn_input_port": {
|
|
||||||
firewall: Firewall{
|
|
||||||
VPNInputPorts: []uint16{0},
|
|
||||||
},
|
|
||||||
errWrapped: ErrFirewallZeroPort,
|
|
||||||
errMessage: "VPN input ports: cannot have a zero port",
|
|
||||||
},
|
|
||||||
"zero_input_port": {
|
|
||||||
firewall: Firewall{
|
|
||||||
InputPorts: []uint16{0},
|
|
||||||
},
|
|
||||||
errWrapped: ErrFirewallZeroPort,
|
|
||||||
errMessage: "input ports: cannot have a zero port",
|
|
||||||
},
|
|
||||||
"unspecified_outbound_subnet": {
|
|
||||||
firewall: Firewall{
|
|
||||||
OutboundSubnets: []netip.Prefix{
|
|
||||||
netip.MustParsePrefix("0.0.0.0/0"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
errWrapped: ErrFirewallPublicOutboundSubnet,
|
|
||||||
errMessage: "outbound subnet has an unspecified address: 0.0.0.0/0",
|
|
||||||
},
|
|
||||||
"public_outbound_subnet": {
|
|
||||||
firewall: Firewall{
|
|
||||||
OutboundSubnets: []netip.Prefix{
|
|
||||||
netip.MustParsePrefix("1.2.3.4/32"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"valid_settings": {
|
|
||||||
firewall: Firewall{
|
|
||||||
VPNInputPorts: []uint16{100, 101},
|
|
||||||
InputPorts: []uint16{200, 201},
|
|
||||||
OutboundSubnets: []netip.Prefix{
|
|
||||||
netip.MustParsePrefix("192.168.1.0/24"),
|
|
||||||
netip.MustParsePrefix("10.10.1.1/32"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for name, testCase := range testCases {
|
|
||||||
testCase := testCase
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
err := testCase.firewall.validate()
|
|
||||||
|
|
||||||
assert.ErrorIs(t, err, testCase.errWrapped)
|
|
||||||
if testCase.errWrapped != nil {
|
|
||||||
assert.EqualError(t, err, testCase.errMessage)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,9 +3,8 @@ package settings
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/qdm12/gosettings"
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||||
"github.com/qdm12/gotree"
|
"github.com/qdm12/gotree"
|
||||||
"github.com/qdm12/govalid/address"
|
"github.com/qdm12/govalid/address"
|
||||||
)
|
)
|
||||||
@@ -16,36 +15,25 @@ type Health struct {
|
|||||||
// for the health check server.
|
// for the health check server.
|
||||||
// It cannot be the empty string in the internal state.
|
// It cannot be the empty string in the internal state.
|
||||||
ServerAddress string
|
ServerAddress string
|
||||||
// ReadHeaderTimeout is the HTTP server header read timeout
|
|
||||||
// duration of the HTTP server. It defaults to 100 milliseconds.
|
|
||||||
ReadHeaderTimeout time.Duration
|
|
||||||
// ReadTimeout is the HTTP read timeout duration of the
|
|
||||||
// HTTP server. It defaults to 500 milliseconds.
|
|
||||||
ReadTimeout time.Duration
|
|
||||||
// TargetAddress is the address (host or host:port)
|
// TargetAddress is the address (host or host:port)
|
||||||
// to TCP dial to periodically for the health check.
|
// to TCP dial to periodically for the health check.
|
||||||
// It cannot be the empty string in the internal state.
|
// It cannot be the empty string in the internal state.
|
||||||
TargetAddress string
|
TargetAddress string
|
||||||
// SuccessWait is the duration to wait to re-run the
|
VPN HealthyWait
|
||||||
// healthcheck after a successful healthcheck.
|
|
||||||
// It defaults to 5 seconds and cannot be zero in
|
|
||||||
// the internal state.
|
|
||||||
SuccessWait time.Duration
|
|
||||||
// VPN has health settings specific to the VPN loop.
|
|
||||||
VPN HealthyWait
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h Health) Validate() (err error) {
|
func (h Health) Validate() (err error) {
|
||||||
uid := os.Getuid()
|
uid := os.Getuid()
|
||||||
err = address.Validate(h.ServerAddress,
|
_, err = address.Validate(h.ServerAddress,
|
||||||
address.OptionListening(uid))
|
address.OptionListening(uid))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("server listening address is not valid: %w", err)
|
return fmt.Errorf("%w: %s",
|
||||||
|
ErrServerAddressNotValid, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = h.VPN.validate()
|
err = h.VPN.validate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("health VPN settings: %w", err)
|
return fmt.Errorf("health VPN settings validation failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -53,23 +41,17 @@ func (h Health) Validate() (err error) {
|
|||||||
|
|
||||||
func (h *Health) copy() (copied Health) {
|
func (h *Health) copy() (copied Health) {
|
||||||
return Health{
|
return Health{
|
||||||
ServerAddress: h.ServerAddress,
|
ServerAddress: h.ServerAddress,
|
||||||
ReadHeaderTimeout: h.ReadHeaderTimeout,
|
TargetAddress: h.TargetAddress,
|
||||||
ReadTimeout: h.ReadTimeout,
|
VPN: h.VPN.copy(),
|
||||||
TargetAddress: h.TargetAddress,
|
|
||||||
SuccessWait: h.SuccessWait,
|
|
||||||
VPN: h.VPN.copy(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MergeWith merges the other settings into any
|
// MergeWith merges the other settings into any
|
||||||
// unset field of the receiver settings object.
|
// unset field of the receiver settings object.
|
||||||
func (h *Health) MergeWith(other Health) {
|
func (h *Health) MergeWith(other Health) {
|
||||||
h.ServerAddress = gosettings.MergeWithString(h.ServerAddress, other.ServerAddress)
|
h.ServerAddress = helpers.MergeWithString(h.ServerAddress, other.ServerAddress)
|
||||||
h.ReadHeaderTimeout = gosettings.MergeWithNumber(h.ReadHeaderTimeout, other.ReadHeaderTimeout)
|
h.TargetAddress = helpers.MergeWithString(h.TargetAddress, other.TargetAddress)
|
||||||
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)
|
h.VPN.mergeWith(other.VPN)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,23 +59,14 @@ func (h *Health) MergeWith(other Health) {
|
|||||||
// settings object with any field set in the other
|
// settings object with any field set in the other
|
||||||
// settings.
|
// settings.
|
||||||
func (h *Health) OverrideWith(other Health) {
|
func (h *Health) OverrideWith(other Health) {
|
||||||
h.ServerAddress = gosettings.OverrideWithString(h.ServerAddress, other.ServerAddress)
|
h.ServerAddress = helpers.OverrideWithString(h.ServerAddress, other.ServerAddress)
|
||||||
h.ReadHeaderTimeout = gosettings.OverrideWithNumber(h.ReadHeaderTimeout, other.ReadHeaderTimeout)
|
h.TargetAddress = helpers.OverrideWithString(h.TargetAddress, other.TargetAddress)
|
||||||
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)
|
h.VPN.overrideWith(other.VPN)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Health) SetDefaults() {
|
func (h *Health) SetDefaults() {
|
||||||
h.ServerAddress = gosettings.DefaultString(h.ServerAddress, "127.0.0.1:9999")
|
h.ServerAddress = helpers.DefaultString(h.ServerAddress, "127.0.0.1:9999")
|
||||||
const defaultReadHeaderTimeout = 100 * time.Millisecond
|
h.TargetAddress = helpers.DefaultString(h.TargetAddress, "github.com:443")
|
||||||
h.ReadHeaderTimeout = gosettings.DefaultNumber(h.ReadHeaderTimeout, defaultReadHeaderTimeout)
|
|
||||||
const defaultReadTimeout = 500 * time.Millisecond
|
|
||||||
h.ReadTimeout = gosettings.DefaultNumber(h.ReadTimeout, defaultReadTimeout)
|
|
||||||
h.TargetAddress = gosettings.DefaultString(h.TargetAddress, "cloudflare.com:443")
|
|
||||||
const defaultSuccessWait = 5 * time.Second
|
|
||||||
h.SuccessWait = gosettings.DefaultNumber(h.SuccessWait, defaultSuccessWait)
|
|
||||||
h.VPN.setDefaults()
|
h.VPN.setDefaults()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,9 +78,6 @@ func (h Health) toLinesNode() (node *gotree.Node) {
|
|||||||
node = gotree.New("Health settings:")
|
node = gotree.New("Health settings:")
|
||||||
node.Appendf("Server listening address: %s", h.ServerAddress)
|
node.Appendf("Server listening address: %s", h.ServerAddress)
|
||||||
node.Appendf("Target address: %s", h.TargetAddress)
|
node.Appendf("Target address: %s", h.TargetAddress)
|
||||||
node.Appendf("Duration to wait after success: %s", h.SuccessWait)
|
|
||||||
node.Appendf("Read header timeout: %s", h.ReadHeaderTimeout)
|
|
||||||
node.Appendf("Read timeout: %s", h.ReadTimeout)
|
|
||||||
node.AppendNode(h.VPN.toLinesNode("VPN"))
|
node.AppendNode(h.VPN.toLinesNode("VPN"))
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package settings
|
|||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/qdm12/gosettings"
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||||
"github.com/qdm12/gotree"
|
"github.com/qdm12/gotree"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -27,31 +27,31 @@ func (h HealthyWait) validate() (err error) {
|
|||||||
// unset field of the receiver settings object.
|
// unset field of the receiver settings object.
|
||||||
func (h *HealthyWait) copy() (copied HealthyWait) {
|
func (h *HealthyWait) copy() (copied HealthyWait) {
|
||||||
return HealthyWait{
|
return HealthyWait{
|
||||||
Initial: gosettings.CopyPointer(h.Initial),
|
Initial: helpers.CopyDurationPtr(h.Initial),
|
||||||
Addition: gosettings.CopyPointer(h.Addition),
|
Addition: helpers.CopyDurationPtr(h.Addition),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// mergeWith merges the other settings into any
|
// mergeWith merges the other settings into any
|
||||||
// unset field of the receiver settings object.
|
// unset field of the receiver settings object.
|
||||||
func (h *HealthyWait) mergeWith(other HealthyWait) {
|
func (h *HealthyWait) mergeWith(other HealthyWait) {
|
||||||
h.Initial = gosettings.MergeWithPointer(h.Initial, other.Initial)
|
h.Initial = helpers.MergeWithDuration(h.Initial, other.Initial)
|
||||||
h.Addition = gosettings.MergeWithPointer(h.Addition, other.Addition)
|
h.Addition = helpers.MergeWithDuration(h.Addition, other.Addition)
|
||||||
}
|
}
|
||||||
|
|
||||||
// overrideWith overrides fields of the receiver
|
// overrideWith overrides fields of the receiver
|
||||||
// settings object with any field set in the other
|
// settings object with any field set in the other
|
||||||
// settings.
|
// settings.
|
||||||
func (h *HealthyWait) overrideWith(other HealthyWait) {
|
func (h *HealthyWait) overrideWith(other HealthyWait) {
|
||||||
h.Initial = gosettings.OverrideWithPointer(h.Initial, other.Initial)
|
h.Initial = helpers.OverrideWithDuration(h.Initial, other.Initial)
|
||||||
h.Addition = gosettings.OverrideWithPointer(h.Addition, other.Addition)
|
h.Addition = helpers.OverrideWithDuration(h.Addition, other.Addition)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *HealthyWait) setDefaults() {
|
func (h *HealthyWait) setDefaults() {
|
||||||
const initialDurationDefault = 6 * time.Second
|
const initialDurationDefault = 6 * time.Second
|
||||||
const additionDurationDefault = 5 * time.Second
|
const additionDurationDefault = 5 * time.Second
|
||||||
h.Initial = gosettings.DefaultPointer(h.Initial, initialDurationDefault)
|
h.Initial = helpers.DefaultDuration(h.Initial, initialDurationDefault)
|
||||||
h.Addition = gosettings.DefaultPointer(h.Addition, additionDurationDefault)
|
h.Addition = helpers.DefaultDuration(h.Addition, additionDurationDefault)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h HealthyWait) String() string {
|
func (h HealthyWait) String() string {
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
package helpers
|
package helpers
|
||||||
|
|
||||||
func IsOneOf[T comparable](value T, choices ...T) (ok bool) {
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func IsOneOf(value string, choices ...string) (ok bool) {
|
||||||
for _, choice := range choices {
|
for _, choice := range choices {
|
||||||
if value == choice {
|
if value == choice {
|
||||||
return true
|
return true
|
||||||
@@ -8,3 +14,32 @@ func IsOneOf[T comparable](value T, choices ...T) (ok bool) {
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var ErrValueNotOneOf = errors.New("value is not one of the possible choices")
|
||||||
|
|
||||||
|
func AreAllOneOf(values, choices []string) (err error) {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|||||||
190
internal/configuration/settings/helpers/copy.go
Normal file
190
internal/configuration/settings/helpers/copy.go
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
package helpers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/qdm12/golibs/logging"
|
||||||
|
"inet.af/netaddr"
|
||||||
|
)
|
||||||
|
|
||||||
|
func CopyStringPtr(original *string) (copied *string) {
|
||||||
|
if original == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
copied = new(string)
|
||||||
|
*copied = *original
|
||||||
|
return copied
|
||||||
|
}
|
||||||
|
|
||||||
|
func CopyBoolPtr(original *bool) (copied *bool) {
|
||||||
|
if original == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
copied = new(bool)
|
||||||
|
*copied = *original
|
||||||
|
return copied
|
||||||
|
}
|
||||||
|
|
||||||
|
func CopyUint8Ptr(original *uint8) (copied *uint8) {
|
||||||
|
if original == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
copied = new(uint8)
|
||||||
|
*copied = *original
|
||||||
|
return copied
|
||||||
|
}
|
||||||
|
|
||||||
|
func CopyUint16Ptr(original *uint16) (copied *uint16) {
|
||||||
|
if original == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
copied = new(uint16)
|
||||||
|
*copied = *original
|
||||||
|
return copied
|
||||||
|
}
|
||||||
|
|
||||||
|
func CopyIntPtr(original *int) (copied *int) {
|
||||||
|
if original == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
copied = new(int)
|
||||||
|
*copied = *original
|
||||||
|
return copied
|
||||||
|
}
|
||||||
|
|
||||||
|
func CopyDurationPtr(original *time.Duration) (copied *time.Duration) {
|
||||||
|
if original == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
copied = new(time.Duration)
|
||||||
|
*copied = *original
|
||||||
|
return copied
|
||||||
|
}
|
||||||
|
|
||||||
|
func CopyLogLevelPtr(original *logging.Level) (copied *logging.Level) {
|
||||||
|
if original == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
copied = new(logging.Level)
|
||||||
|
*copied = *original
|
||||||
|
return copied
|
||||||
|
}
|
||||||
|
|
||||||
|
func CopyIP(original net.IP) (copied net.IP) {
|
||||||
|
if original == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
copied = make(net.IP, len(original))
|
||||||
|
copy(copied, original)
|
||||||
|
return copied
|
||||||
|
}
|
||||||
|
|
||||||
|
func CopyIPNet(original net.IPNet) (copied net.IPNet) {
|
||||||
|
if original.IP != nil {
|
||||||
|
copied.IP = make(net.IP, len(original.IP))
|
||||||
|
copy(copied.IP, original.IP)
|
||||||
|
}
|
||||||
|
|
||||||
|
if original.Mask != nil {
|
||||||
|
copied.Mask = make(net.IPMask, len(original.Mask))
|
||||||
|
copy(copied.Mask, original.Mask)
|
||||||
|
}
|
||||||
|
|
||||||
|
return copied
|
||||||
|
}
|
||||||
|
|
||||||
|
func CopyIPNetPtr(original *net.IPNet) (copied *net.IPNet) {
|
||||||
|
if original == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
copied = new(net.IPNet)
|
||||||
|
*copied = CopyIPNet(*original)
|
||||||
|
return copied
|
||||||
|
}
|
||||||
|
|
||||||
|
func CopyNetaddrIP(original netaddr.IP) (copied netaddr.IP) {
|
||||||
|
b, err := original.MarshalBinary()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = copied.UnmarshalBinary(b)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return copied
|
||||||
|
}
|
||||||
|
|
||||||
|
func CopyIPPrefix(original netaddr.IPPrefix) (copied netaddr.IPPrefix) {
|
||||||
|
b, err := original.MarshalText()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = copied.UnmarshalText(b)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return copied
|
||||||
|
}
|
||||||
|
|
||||||
|
func CopyStringSlice(original []string) (copied []string) {
|
||||||
|
if original == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
copied = make([]string, len(original))
|
||||||
|
copy(copied, original)
|
||||||
|
return copied
|
||||||
|
}
|
||||||
|
|
||||||
|
func CopyUint16Slice(original []uint16) (copied []uint16) {
|
||||||
|
if original == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
copied = make([]uint16, len(original))
|
||||||
|
copy(copied, original)
|
||||||
|
return copied
|
||||||
|
}
|
||||||
|
|
||||||
|
func CopyIPNetSlice(original []net.IPNet) (copied []net.IPNet) {
|
||||||
|
if original == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
copied = make([]net.IPNet, len(original))
|
||||||
|
for i := range original {
|
||||||
|
copied[i] = CopyIPNet(original[i])
|
||||||
|
}
|
||||||
|
return copied
|
||||||
|
}
|
||||||
|
|
||||||
|
func CopyIPPrefixSlice(original []netaddr.IPPrefix) (copied []netaddr.IPPrefix) {
|
||||||
|
if original == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
copied = make([]netaddr.IPPrefix, len(original))
|
||||||
|
for i := range original {
|
||||||
|
copied[i] = CopyIPPrefix(original[i])
|
||||||
|
}
|
||||||
|
return copied
|
||||||
|
}
|
||||||
|
|
||||||
|
func CopyNetaddrIPsSlice(original []netaddr.IP) (copied []netaddr.IP) {
|
||||||
|
if original == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
copied = make([]netaddr.IP, len(original))
|
||||||
|
for i := range original {
|
||||||
|
copied[i] = CopyNetaddrIP(original[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
return copied
|
||||||
|
}
|
||||||
93
internal/configuration/settings/helpers/default.go
Normal file
93
internal/configuration/settings/helpers/default.go
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
package helpers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/qdm12/golibs/logging"
|
||||||
|
)
|
||||||
|
|
||||||
|
func DefaultInt(existing *int, defaultValue int) (
|
||||||
|
result *int) {
|
||||||
|
if existing != nil {
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
result = new(int)
|
||||||
|
*result = defaultValue
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func DefaultUint8(existing *uint8, defaultValue uint8) (
|
||||||
|
result *uint8) {
|
||||||
|
if existing != nil {
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
result = new(uint8)
|
||||||
|
*result = defaultValue
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func DefaultUint16(existing *uint16, defaultValue uint16) (
|
||||||
|
result *uint16) {
|
||||||
|
if existing != nil {
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
result = new(uint16)
|
||||||
|
*result = defaultValue
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func DefaultBool(existing *bool, defaultValue bool) (
|
||||||
|
result *bool) {
|
||||||
|
if existing != nil {
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
result = new(bool)
|
||||||
|
*result = defaultValue
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func DefaultString(existing string, defaultValue string) (
|
||||||
|
result string) {
|
||||||
|
if existing != "" {
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func DefaultStringPtr(existing *string, defaultValue string) (result *string) {
|
||||||
|
if existing != nil {
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
result = new(string)
|
||||||
|
*result = defaultValue
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func DefaultDuration(existing *time.Duration,
|
||||||
|
defaultValue time.Duration) (result *time.Duration) {
|
||||||
|
if existing != nil {
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
result = new(time.Duration)
|
||||||
|
*result = defaultValue
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func DefaultLogLevel(existing *logging.Level,
|
||||||
|
defaultValue logging.Level) (result *logging.Level) {
|
||||||
|
if existing != nil {
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
result = new(logging.Level)
|
||||||
|
*result = defaultValue
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func DefaultIP(existing net.IP, defaultValue net.IP) (
|
||||||
|
result net.IP) {
|
||||||
|
if existing != nil {
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
31
internal/configuration/settings/helpers/files.go
Normal file
31
internal/configuration/settings/helpers/files.go
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
226
internal/configuration/settings/helpers/merge.go
Normal file
226
internal/configuration/settings/helpers/merge.go
Normal file
@@ -0,0 +1,226 @@
|
|||||||
|
package helpers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/qdm12/golibs/logging"
|
||||||
|
"inet.af/netaddr"
|
||||||
|
)
|
||||||
|
|
||||||
|
func MergeWithBool(existing, other *bool) (result *bool) {
|
||||||
|
if existing != nil {
|
||||||
|
return existing
|
||||||
|
} else if other == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
result = new(bool)
|
||||||
|
*result = *other
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func MergeWithString(existing, other string) (result string) {
|
||||||
|
if existing != "" {
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
return other
|
||||||
|
}
|
||||||
|
|
||||||
|
func MergeWithStringPtr(existing, other *string) (result *string) {
|
||||||
|
if existing != nil {
|
||||||
|
return existing
|
||||||
|
} else if other == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
result = new(string)
|
||||||
|
*result = *other
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func MergeWithInt(existing, other *int) (result *int) {
|
||||||
|
if existing != nil {
|
||||||
|
return existing
|
||||||
|
} else if other == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
result = new(int)
|
||||||
|
*result = *other
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func MergeWithUint8(existing, other *uint8) (result *uint8) {
|
||||||
|
if existing != nil {
|
||||||
|
return existing
|
||||||
|
} else if other == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
result = new(uint8)
|
||||||
|
*result = *other
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func MergeWithUint16(existing, other *uint16) (result *uint16) {
|
||||||
|
if existing != nil {
|
||||||
|
return existing
|
||||||
|
} else if other == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
result = new(uint16)
|
||||||
|
*result = *other
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func MergeWithIP(existing, other net.IP) (result net.IP) {
|
||||||
|
if existing != nil {
|
||||||
|
return existing
|
||||||
|
} else if other == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
result = make(net.IP, len(other))
|
||||||
|
copy(result, other)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func MergeWithDuration(existing, other *time.Duration) (result *time.Duration) {
|
||||||
|
if existing != nil {
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
return other
|
||||||
|
}
|
||||||
|
|
||||||
|
func MergeWithLogLevel(existing, other *logging.Level) (result *logging.Level) {
|
||||||
|
if existing != nil {
|
||||||
|
return existing
|
||||||
|
} else if other == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
result = new(logging.Level)
|
||||||
|
*result = *other
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func MergeStringSlices(a, b []string) (result []string) {
|
||||||
|
if a == nil && b == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
seen := make(map[string]struct{}, len(a)+len(b))
|
||||||
|
result = make([]string, 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
|
||||||
|
}
|
||||||
|
|
||||||
|
func MergeUint16Slices(a, b []uint16) (result []uint16) {
|
||||||
|
if a == nil && b == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
seen := make(map[uint16]struct{}, len(a)+len(b))
|
||||||
|
result = make([]uint16, 0, len(a)+len(b))
|
||||||
|
for _, n := range a {
|
||||||
|
if _, ok := seen[n]; ok {
|
||||||
|
continue // duplicate
|
||||||
|
}
|
||||||
|
result = append(result, n)
|
||||||
|
seen[n] = struct{}{}
|
||||||
|
}
|
||||||
|
for _, n := range b {
|
||||||
|
if _, ok := seen[n]; ok {
|
||||||
|
continue // duplicate
|
||||||
|
}
|
||||||
|
result = append(result, n)
|
||||||
|
seen[n] = struct{}{}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func MergeIPNetsSlices(a, b []net.IPNet) (result []net.IPNet) {
|
||||||
|
if a == nil && b == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
seen := make(map[string]struct{}, len(a)+len(b))
|
||||||
|
result = make([]net.IPNet, 0, len(a)+len(b))
|
||||||
|
for _, ipNet := range a {
|
||||||
|
key := ipNet.String()
|
||||||
|
if _, ok := seen[key]; ok {
|
||||||
|
continue // duplicate
|
||||||
|
}
|
||||||
|
result = append(result, ipNet)
|
||||||
|
seen[key] = struct{}{}
|
||||||
|
}
|
||||||
|
for _, ipNet := range b {
|
||||||
|
key := ipNet.String()
|
||||||
|
if _, ok := seen[key]; ok {
|
||||||
|
continue // duplicate
|
||||||
|
}
|
||||||
|
result = append(result, ipNet)
|
||||||
|
seen[key] = struct{}{}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func MergeNetaddrIPsSlices(a, b []netaddr.IP) (result []netaddr.IP) {
|
||||||
|
if a == nil && b == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
seen := make(map[string]struct{}, len(a)+len(b))
|
||||||
|
result = make([]netaddr.IP, 0, len(a)+len(b))
|
||||||
|
for _, ip := range a {
|
||||||
|
key := ip.String()
|
||||||
|
if _, ok := seen[key]; ok {
|
||||||
|
continue // duplicate
|
||||||
|
}
|
||||||
|
result = append(result, ip)
|
||||||
|
seen[key] = struct{}{}
|
||||||
|
}
|
||||||
|
for _, ip := range b {
|
||||||
|
key := ip.String()
|
||||||
|
if _, ok := seen[key]; ok {
|
||||||
|
continue // duplicate
|
||||||
|
}
|
||||||
|
result = append(result, ip)
|
||||||
|
seen[key] = struct{}{}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func MergeIPPrefixesSlices(a, b []netaddr.IPPrefix) (result []netaddr.IPPrefix) {
|
||||||
|
if a == nil && b == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
seen := make(map[string]struct{}, len(a)+len(b))
|
||||||
|
result = make([]netaddr.IPPrefix, 0, len(a)+len(b))
|
||||||
|
for _, ipPrefix := range a {
|
||||||
|
key := ipPrefix.String()
|
||||||
|
if _, ok := seen[key]; ok {
|
||||||
|
continue // duplicate
|
||||||
|
}
|
||||||
|
result = append(result, ipPrefix)
|
||||||
|
seen[key] = struct{}{}
|
||||||
|
}
|
||||||
|
for _, ipPrefix := range b {
|
||||||
|
key := ipPrefix.String()
|
||||||
|
if _, ok := seen[key]; ok {
|
||||||
|
continue // duplicate
|
||||||
|
}
|
||||||
|
result = append(result, ipPrefix)
|
||||||
|
seen[key] = struct{}{}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
29
internal/configuration/settings/helpers/messages.go
Normal file
29
internal/configuration/settings/helpers/messages.go
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
25
internal/configuration/settings/helpers/obfuscate.go
Normal file
25
internal/configuration/settings/helpers/obfuscate.go
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
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]"
|
||||||
|
}
|
||||||
133
internal/configuration/settings/helpers/override.go
Normal file
133
internal/configuration/settings/helpers/override.go
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
package helpers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/qdm12/golibs/logging"
|
||||||
|
"inet.af/netaddr"
|
||||||
|
)
|
||||||
|
|
||||||
|
func OverrideWithBool(existing, other *bool) (result *bool) {
|
||||||
|
if other == nil {
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
result = new(bool)
|
||||||
|
*result = *other
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func OverrideWithString(existing, other string) (result string) {
|
||||||
|
if other == "" {
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
return other
|
||||||
|
}
|
||||||
|
|
||||||
|
func OverrideWithStringPtr(existing, other *string) (result *string) {
|
||||||
|
if other == nil {
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
result = new(string)
|
||||||
|
*result = *other
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func OverrideWithInt(existing, other *int) (result *int) {
|
||||||
|
if other == nil {
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
result = new(int)
|
||||||
|
*result = *other
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func OverrideWithUint8(existing, other *uint8) (result *uint8) {
|
||||||
|
if other == nil {
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
result = new(uint8)
|
||||||
|
*result = *other
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func OverrideWithUint16(existing, other *uint16) (result *uint16) {
|
||||||
|
if other == nil {
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
result = new(uint16)
|
||||||
|
*result = *other
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func OverrideWithIP(existing, other net.IP) (result net.IP) {
|
||||||
|
if other == nil {
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
result = make(net.IP, len(other))
|
||||||
|
copy(result, other)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func OverrideWithDuration(existing, other *time.Duration) (result *time.Duration) {
|
||||||
|
if other == nil {
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
result = new(time.Duration)
|
||||||
|
*result = *other
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func OverrideWithLogLevel(existing, other *logging.Level) (result *logging.Level) {
|
||||||
|
if other == nil {
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
result = new(logging.Level)
|
||||||
|
*result = *other
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func OverrideWithStringSlice(existing, other []string) (result []string) {
|
||||||
|
if other == nil {
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
result = make([]string, len(other))
|
||||||
|
copy(result, other)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func OverrideWithUint16Slice(existing, other []uint16) (result []uint16) {
|
||||||
|
if other == nil {
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
result = make([]uint16, len(other))
|
||||||
|
copy(result, other)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func OverrideWithIPNetsSlice(existing, other []net.IPNet) (result []net.IPNet) {
|
||||||
|
if other == nil {
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
result = make([]net.IPNet, len(other))
|
||||||
|
copy(result, other)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func OverrideWithNetaddrIPsSlice(existing, other []netaddr.IP) (result []netaddr.IP) {
|
||||||
|
if other == nil {
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
result = make([]netaddr.IP, len(other))
|
||||||
|
copy(result, other)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func OverrideWithIPPrefixesSlice(existing, other []netaddr.IPPrefix) (result []netaddr.IPPrefix) {
|
||||||
|
if other == nil {
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
result = make([]netaddr.IPPrefix, len(other))
|
||||||
|
copy(result, other)
|
||||||
|
return result
|
||||||
|
}
|
||||||
11
internal/configuration/settings/helpers/pointers.go
Normal file
11
internal/configuration/settings/helpers/pointers.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package helpers
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// StringPtr returns a pointer to the string value
|
||||||
|
// passed as argument.
|
||||||
|
func StringPtr(s string) *string { return &s }
|
||||||
|
|
||||||
|
// DurationPtr returns a pointer to the duration value
|
||||||
|
// passed as argument.
|
||||||
|
func DurationPtr(d time.Duration) *time.Duration { return &d }
|
||||||
@@ -1,5 +1,12 @@
|
|||||||
package helpers
|
package helpers
|
||||||
|
|
||||||
|
func BoolPtrToYesNo(b *bool) string {
|
||||||
|
if *b {
|
||||||
|
return "yes"
|
||||||
|
}
|
||||||
|
return "no"
|
||||||
|
}
|
||||||
|
|
||||||
func TCPPtrToString(tcp *bool) string {
|
func TCPPtrToString(tcp *bool) string {
|
||||||
if *tcp {
|
if *tcp {
|
||||||
return "TCP"
|
return "TCP"
|
||||||
|
|||||||
@@ -3,9 +3,8 @@ package settings
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/qdm12/gosettings"
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||||
"github.com/qdm12/gotree"
|
"github.com/qdm12/gotree"
|
||||||
"github.com/qdm12/govalid/address"
|
"github.com/qdm12/govalid/address"
|
||||||
)
|
)
|
||||||
@@ -34,21 +33,16 @@ type HTTPProxy struct {
|
|||||||
// each request/response. It cannot be nil in the
|
// each request/response. It cannot be nil in the
|
||||||
// internal state.
|
// internal state.
|
||||||
Log *bool
|
Log *bool
|
||||||
// ReadHeaderTimeout is the HTTP header read timeout duration
|
|
||||||
// of the HTTP server. It defaults to 1 second if left unset.
|
|
||||||
ReadHeaderTimeout time.Duration
|
|
||||||
// ReadTimeout is the HTTP read timeout duration
|
|
||||||
// of the HTTP server. It defaults to 3 seconds if left unset.
|
|
||||||
ReadTimeout time.Duration
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h HTTPProxy) validate() (err error) {
|
func (h HTTPProxy) validate() (err error) {
|
||||||
// Do not validate user and password
|
// Do not validate user and password
|
||||||
|
|
||||||
uid := os.Getuid()
|
uid := os.Getuid()
|
||||||
err = address.Validate(h.ListeningAddress, address.OptionListening(uid))
|
_, err = address.Validate(h.ListeningAddress, address.OptionListening(uid))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%w: %s", ErrServerAddressNotValid, h.ListeningAddress)
|
return fmt.Errorf("%w: %s",
|
||||||
|
ErrServerAddressNotValid, h.ListeningAddress)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -56,55 +50,45 @@ func (h HTTPProxy) validate() (err error) {
|
|||||||
|
|
||||||
func (h *HTTPProxy) copy() (copied HTTPProxy) {
|
func (h *HTTPProxy) copy() (copied HTTPProxy) {
|
||||||
return HTTPProxy{
|
return HTTPProxy{
|
||||||
User: gosettings.CopyPointer(h.User),
|
User: helpers.CopyStringPtr(h.User),
|
||||||
Password: gosettings.CopyPointer(h.Password),
|
Password: helpers.CopyStringPtr(h.Password),
|
||||||
ListeningAddress: h.ListeningAddress,
|
ListeningAddress: h.ListeningAddress,
|
||||||
Enabled: gosettings.CopyPointer(h.Enabled),
|
Enabled: helpers.CopyBoolPtr(h.Enabled),
|
||||||
Stealth: gosettings.CopyPointer(h.Stealth),
|
Stealth: helpers.CopyBoolPtr(h.Stealth),
|
||||||
Log: gosettings.CopyPointer(h.Log),
|
Log: helpers.CopyBoolPtr(h.Log),
|
||||||
ReadHeaderTimeout: h.ReadHeaderTimeout,
|
|
||||||
ReadTimeout: h.ReadTimeout,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// mergeWith merges the other settings into any
|
// mergeWith merges the other settings into any
|
||||||
// unset field of the receiver settings object.
|
// unset field of the receiver settings object.
|
||||||
func (h *HTTPProxy) mergeWith(other HTTPProxy) {
|
func (h *HTTPProxy) mergeWith(other HTTPProxy) {
|
||||||
h.User = gosettings.MergeWithPointer(h.User, other.User)
|
h.User = helpers.MergeWithStringPtr(h.User, other.User)
|
||||||
h.Password = gosettings.MergeWithPointer(h.Password, other.Password)
|
h.Password = helpers.MergeWithStringPtr(h.Password, other.Password)
|
||||||
h.ListeningAddress = gosettings.MergeWithString(h.ListeningAddress, other.ListeningAddress)
|
h.ListeningAddress = helpers.MergeWithString(h.ListeningAddress, other.ListeningAddress)
|
||||||
h.Enabled = gosettings.MergeWithPointer(h.Enabled, other.Enabled)
|
h.Enabled = helpers.MergeWithBool(h.Enabled, other.Enabled)
|
||||||
h.Stealth = gosettings.MergeWithPointer(h.Stealth, other.Stealth)
|
h.Stealth = helpers.MergeWithBool(h.Stealth, other.Stealth)
|
||||||
h.Log = gosettings.MergeWithPointer(h.Log, other.Log)
|
h.Log = helpers.MergeWithBool(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
|
// overrideWith overrides fields of the receiver
|
||||||
// settings object with any field set in the other
|
// settings object with any field set in the other
|
||||||
// settings.
|
// settings.
|
||||||
func (h *HTTPProxy) overrideWith(other HTTPProxy) {
|
func (h *HTTPProxy) overrideWith(other HTTPProxy) {
|
||||||
h.User = gosettings.OverrideWithPointer(h.User, other.User)
|
h.User = helpers.OverrideWithStringPtr(h.User, other.User)
|
||||||
h.Password = gosettings.OverrideWithPointer(h.Password, other.Password)
|
h.Password = helpers.OverrideWithStringPtr(h.Password, other.Password)
|
||||||
h.ListeningAddress = gosettings.OverrideWithString(h.ListeningAddress, other.ListeningAddress)
|
h.ListeningAddress = helpers.OverrideWithString(h.ListeningAddress, other.ListeningAddress)
|
||||||
h.Enabled = gosettings.OverrideWithPointer(h.Enabled, other.Enabled)
|
h.Enabled = helpers.OverrideWithBool(h.Enabled, other.Enabled)
|
||||||
h.Stealth = gosettings.OverrideWithPointer(h.Stealth, other.Stealth)
|
h.Stealth = helpers.OverrideWithBool(h.Stealth, other.Stealth)
|
||||||
h.Log = gosettings.OverrideWithPointer(h.Log, other.Log)
|
h.Log = helpers.OverrideWithBool(h.Log, other.Log)
|
||||||
h.ReadHeaderTimeout = gosettings.OverrideWithNumber(h.ReadHeaderTimeout, other.ReadHeaderTimeout)
|
|
||||||
h.ReadTimeout = gosettings.OverrideWithNumber(h.ReadTimeout, other.ReadTimeout)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *HTTPProxy) setDefaults() {
|
func (h *HTTPProxy) setDefaults() {
|
||||||
h.User = gosettings.DefaultPointer(h.User, "")
|
h.User = helpers.DefaultStringPtr(h.User, "")
|
||||||
h.Password = gosettings.DefaultPointer(h.Password, "")
|
h.Password = helpers.DefaultStringPtr(h.Password, "")
|
||||||
h.ListeningAddress = gosettings.DefaultString(h.ListeningAddress, ":8888")
|
h.ListeningAddress = helpers.DefaultString(h.ListeningAddress, ":8888")
|
||||||
h.Enabled = gosettings.DefaultPointer(h.Enabled, false)
|
h.Enabled = helpers.DefaultBool(h.Enabled, false)
|
||||||
h.Stealth = gosettings.DefaultPointer(h.Stealth, false)
|
h.Stealth = helpers.DefaultBool(h.Stealth, false)
|
||||||
h.Log = gosettings.DefaultPointer(h.Log, false)
|
h.Log = helpers.DefaultBool(h.Log, false)
|
||||||
const defaultReadHeaderTimeout = time.Second
|
|
||||||
h.ReadHeaderTimeout = gosettings.DefaultNumber(h.ReadHeaderTimeout, defaultReadHeaderTimeout)
|
|
||||||
const defaultReadTimeout = 3 * time.Second
|
|
||||||
h.ReadTimeout = gosettings.DefaultNumber(h.ReadTimeout, defaultReadTimeout)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h HTTPProxy) String() string {
|
func (h HTTPProxy) String() string {
|
||||||
@@ -113,18 +97,16 @@ func (h HTTPProxy) String() string {
|
|||||||
|
|
||||||
func (h HTTPProxy) toLinesNode() (node *gotree.Node) {
|
func (h HTTPProxy) toLinesNode() (node *gotree.Node) {
|
||||||
node = gotree.New("HTTP proxy settings:")
|
node = gotree.New("HTTP proxy settings:")
|
||||||
node.Appendf("Enabled: %s", gosettings.BoolToYesNo(h.Enabled))
|
node.Appendf("Enabled: %s", helpers.BoolPtrToYesNo(h.Enabled))
|
||||||
if !*h.Enabled {
|
if !*h.Enabled {
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
|
|
||||||
node.Appendf("Listening address: %s", h.ListeningAddress)
|
node.Appendf("Listening address: %s", h.ListeningAddress)
|
||||||
node.Appendf("User: %s", *h.User)
|
node.Appendf("User: %s", *h.User)
|
||||||
node.Appendf("Password: %s", gosettings.ObfuscateKey(*h.Password))
|
node.Appendf("Password: %s", helpers.ObfuscatePassword(*h.Password))
|
||||||
node.Appendf("Stealth mode: %s", gosettings.BoolToYesNo(h.Stealth))
|
node.Appendf("Stealth mode: %s", helpers.BoolPtrToYesNo(h.Stealth))
|
||||||
node.Appendf("Log: %s", gosettings.BoolToYesNo(h.Log))
|
node.Appendf("Log: %s", helpers.BoolPtrToYesNo(h.Log))
|
||||||
node.Appendf("Read header timeout: %s", h.ReadHeaderTimeout)
|
|
||||||
node.Appendf("Read timeout: %s", h.ReadTimeout)
|
|
||||||
|
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
package settings
|
package settings
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/qdm12/gosettings"
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||||
|
"github.com/qdm12/golibs/logging"
|
||||||
"github.com/qdm12/gotree"
|
"github.com/qdm12/gotree"
|
||||||
"github.com/qdm12/log"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Log contains settings to configure the logger.
|
// Log contains settings to configure the logger.
|
||||||
type Log struct {
|
type Log struct {
|
||||||
// Level is the log level of the logger.
|
// Level is the log level of the logger.
|
||||||
// It cannot be nil in the internal state.
|
// It cannot be nil in the internal state.
|
||||||
Level *log.Level
|
Level *logging.Level
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l Log) validate() (err error) {
|
func (l Log) validate() (err error) {
|
||||||
@@ -19,25 +19,25 @@ func (l Log) validate() (err error) {
|
|||||||
|
|
||||||
func (l *Log) copy() (copied Log) {
|
func (l *Log) copy() (copied Log) {
|
||||||
return Log{
|
return Log{
|
||||||
Level: gosettings.CopyPointer(l.Level),
|
Level: helpers.CopyLogLevelPtr(l.Level),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// mergeWith merges the other settings into any
|
// mergeWith merges the other settings into any
|
||||||
// unset field of the receiver settings object.
|
// unset field of the receiver settings object.
|
||||||
func (l *Log) mergeWith(other Log) {
|
func (l *Log) mergeWith(other Log) {
|
||||||
l.Level = gosettings.MergeWithPointer(l.Level, other.Level)
|
l.Level = helpers.MergeWithLogLevel(l.Level, other.Level)
|
||||||
}
|
}
|
||||||
|
|
||||||
// overrideWith overrides fields of the receiver
|
// overrideWith overrides fields of the receiver
|
||||||
// settings object with any field set in the other
|
// settings object with any field set in the other
|
||||||
// settings.
|
// settings.
|
||||||
func (l *Log) overrideWith(other Log) {
|
func (l *Log) overrideWith(other Log) {
|
||||||
l.Level = gosettings.OverrideWithPointer(l.Level, other.Level)
|
l.Level = helpers.OverrideWithLogLevel(l.Level, other.Level)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Log) setDefaults() {
|
func (l *Log) setDefaults() {
|
||||||
l.Level = gosettings.DefaultPointer(l.Level, log.LevelInfo)
|
l.Level = helpers.DefaultLogLevel(l.Level, logging.LevelInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l Log) String() string {
|
func (l Log) String() string {
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
package settings
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/netip"
|
|
||||||
|
|
||||||
"inet.af/netaddr"
|
|
||||||
)
|
|
||||||
|
|
||||||
func netipAddressToNetaddrIP(address netip.Addr) (ip netaddr.IP) {
|
|
||||||
if address.Is4() {
|
|
||||||
return netaddr.IPFrom4(address.As4())
|
|
||||||
}
|
|
||||||
return netaddr.IPFrom16(address.As16())
|
|
||||||
}
|
|
||||||
|
|
||||||
func netipAddressesToNetaddrIPs(addresses []netip.Addr) (ips []netaddr.IP) {
|
|
||||||
ips = make([]netaddr.IP, len(addresses))
|
|
||||||
for i := range addresses {
|
|
||||||
ips[i] = netipAddressToNetaddrIP(addresses[i])
|
|
||||||
}
|
|
||||||
return ips
|
|
||||||
}
|
|
||||||
|
|
||||||
func netipPrefixToNetaddrIPPrefix(prefix netip.Prefix) (ipPrefix netaddr.IPPrefix) {
|
|
||||||
netaddrIP := netipAddressToNetaddrIP(prefix.Addr())
|
|
||||||
bits := prefix.Bits()
|
|
||||||
return netaddr.IPPrefixFrom(netaddrIP, uint8(bits))
|
|
||||||
}
|
|
||||||
|
|
||||||
func netipPrefixesToNetaddrIPPrefixes(prefixes []netip.Prefix) (ipPrefixes []netaddr.IPPrefix) {
|
|
||||||
ipPrefixes = make([]netaddr.IPPrefix, len(prefixes))
|
|
||||||
for i := range ipPrefixes {
|
|
||||||
ipPrefixes[i] = netipPrefixToNetaddrIPPrefix(prefixes[i])
|
|
||||||
}
|
|
||||||
return ipPrefixes
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,138 +1,146 @@
|
|||||||
package settings
|
package settings
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
"strings"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/constants/openvpn"
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
"github.com/qdm12/gluetun/internal/openvpn/extract"
|
"github.com/qdm12/gluetun/internal/openvpn/parse"
|
||||||
"github.com/qdm12/gluetun/internal/provider/privateinternetaccess/presets"
|
|
||||||
"github.com/qdm12/gosettings"
|
|
||||||
"github.com/qdm12/gosettings/validate"
|
|
||||||
"github.com/qdm12/gotree"
|
"github.com/qdm12/gotree"
|
||||||
)
|
)
|
||||||
|
|
||||||
// OpenVPN contains settings to configure the OpenVPN client.
|
// OpenVPN contains settings to configure the OpenVPN client.
|
||||||
type OpenVPN struct {
|
type OpenVPN struct {
|
||||||
// Version is the OpenVPN version to run.
|
// Version is the OpenVPN version to run.
|
||||||
// It can only be "2.5" or "2.6".
|
// It can only be "2.4" or "2.5".
|
||||||
Version string `json:"version"`
|
Version string
|
||||||
// User is the OpenVPN authentication username.
|
// User is the OpenVPN authentication username.
|
||||||
// It cannot be nil in the internal state if OpenVPN is used.
|
// It cannot be an empty string in the internal state
|
||||||
// It is usually required but in some cases can be the empty string
|
// if OpenVPN is used.
|
||||||
// to indicate no user+password authentication is needed.
|
User string
|
||||||
User *string `json:"user"`
|
|
||||||
// Password is the OpenVPN authentication password.
|
// Password is the OpenVPN authentication password.
|
||||||
// It cannot be nil in the internal state if OpenVPN is used.
|
// It cannot be an empty string in the internal state
|
||||||
// It is usually required but in some cases can be the empty string
|
// if OpenVPN is used.
|
||||||
// to indicate no user+password authentication is needed.
|
Password string
|
||||||
Password *string `json:"password"`
|
|
||||||
// ConfFile is a custom OpenVPN configuration file path.
|
// ConfFile is a custom OpenVPN configuration file path.
|
||||||
// It can be set to the empty string for it to be ignored.
|
// It can be set to the empty string for it to be ignored.
|
||||||
// It cannot be nil in the internal state.
|
// It cannot be nil in the internal state.
|
||||||
ConfFile *string `json:"config_file_path"`
|
ConfFile *string
|
||||||
// Ciphers is a list of ciphers to use for OpenVPN,
|
// Ciphers is a list of ciphers to use for OpenVPN,
|
||||||
// different from the ones specified by the VPN
|
// different from the ones specified by the VPN
|
||||||
// service provider configuration files.
|
// service provider configuration files.
|
||||||
Ciphers []string `json:"ciphers"`
|
Ciphers []string
|
||||||
// Auth is an auth algorithm to use in OpenVPN instead
|
// Auth is an auth algorithm to use in OpenVPN instead
|
||||||
// of the one specified by the VPN service provider.
|
// of the one specified by the VPN service provider.
|
||||||
// It cannot be nil in the internal state.
|
// It cannot be nil in the internal state.
|
||||||
// It is ignored if it is set to the empty string.
|
// It is ignored if it is set to the empty string.
|
||||||
Auth *string `json:"auth"`
|
Auth *string
|
||||||
// Cert is the base64 encoded DER of an OpenVPN certificate for the <cert> block.
|
// ClientCrt is the OpenVPN client certificate.
|
||||||
// This is notably used by Cyberghost and VPN secure.
|
// This is notably used by Cyberghost.
|
||||||
// It can be set to the empty string to be ignored.
|
// It can be set to the empty string to be ignored.
|
||||||
// It cannot be nil in the internal state.
|
// It cannot be nil in the internal state.
|
||||||
Cert *string `json:"cert"`
|
ClientCrt *string
|
||||||
// Key is the base64 encoded DER of an OpenVPN key.
|
// ClientKey is the OpenVPN client key.
|
||||||
// This is used by Cyberghost and VPN Unlimited.
|
// This is used by Cyberghost and VPN Unlimited.
|
||||||
// It can be set to the empty string to be ignored.
|
// It can be set to the empty string to be ignored.
|
||||||
// It cannot be nil in the internal state.
|
// It cannot be nil in the internal state.
|
||||||
Key *string `json:"key"`
|
ClientKey *string
|
||||||
// EncryptedKey is the base64 encoded DER of an encrypted key for OpenVPN.
|
|
||||||
// It is used by VPN secure.
|
|
||||||
// It defaults to the empty string meaning it is not
|
|
||||||
// to be used. KeyPassphrase must be set if this one is set.
|
|
||||||
EncryptedKey *string `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 `json:"key_passphrase"`
|
|
||||||
// PIAEncPreset is the encryption preset for
|
// PIAEncPreset is the encryption preset for
|
||||||
// Private Internet Access. It can be set to an
|
// Private Internet Access. It can be set to an
|
||||||
// empty string for other providers.
|
// empty string for other providers.
|
||||||
PIAEncPreset *string `json:"pia_encryption_preset"`
|
PIAEncPreset *string
|
||||||
|
// IPv6 is set to true if IPv6 routing should be
|
||||||
|
// set to be tunnel in OpenVPN, and false otherwise.
|
||||||
|
// It cannot be nil in the internal state.
|
||||||
|
IPv6 *bool // TODO automate like with Wireguard
|
||||||
// MSSFix is the value (1 to 10000) to set for the
|
// MSSFix is the value (1 to 10000) to set for the
|
||||||
// mssfix option for OpenVPN. It is ignored if set to 0.
|
// mssfix option for OpenVPN. It is ignored if set to 0.
|
||||||
// It cannot be nil in the internal state.
|
// It cannot be nil in the internal state.
|
||||||
MSSFix *uint16 `json:"mssfix"`
|
MSSFix *uint16
|
||||||
// Interface is the OpenVPN device interface name.
|
// Interface is the OpenVPN device interface name.
|
||||||
// It cannot be an empty string in the internal state.
|
// It cannot be an empty string in the internal state.
|
||||||
Interface string `json:"interface"`
|
Interface string
|
||||||
// ProcessUser is the OpenVPN process OS username
|
// Root is true if OpenVPN is to be run as root,
|
||||||
// to use. It cannot be empty in the internal state.
|
// and false otherwise. It cannot be nil in the
|
||||||
// It defaults to 'root'.
|
// internal state.
|
||||||
ProcessUser string `json:"process_user"`
|
Root *bool
|
||||||
|
// ProcUser is the OpenVPN process OS username
|
||||||
|
// to use. It cannot be nil in the internal state.
|
||||||
|
// This is set and injected at runtime.
|
||||||
|
// TODO only use ProcUser and not Root field.
|
||||||
|
ProcUser string
|
||||||
// Verbosity is the OpenVPN verbosity level from 0 to 6.
|
// Verbosity is the OpenVPN verbosity level from 0 to 6.
|
||||||
// It cannot be nil in the internal state.
|
// It cannot be nil in the internal state.
|
||||||
Verbosity *int `json:"verbosity"`
|
Verbosity *int
|
||||||
// Flags is a slice of additional flags to be passed
|
// Flags is a slice of additional flags to be passed
|
||||||
// to the OpenVPN program.
|
// to the OpenVPN program.
|
||||||
Flags []string `json:"flags"`
|
Flags []string
|
||||||
}
|
}
|
||||||
|
|
||||||
var ivpnAccountID = regexp.MustCompile(`^(i|ivpn)\-[a-zA-Z0-9]{4}\-[a-zA-Z0-9]{4}\-[a-zA-Z0-9]{4}$`)
|
|
||||||
|
|
||||||
func (o OpenVPN) validate(vpnProvider string) (err error) {
|
func (o OpenVPN) validate(vpnProvider string) (err error) {
|
||||||
// Validate version
|
// Validate version
|
||||||
validVersions := []string{openvpn.Openvpn25, openvpn.Openvpn26}
|
validVersions := []string{constants.Openvpn24, constants.Openvpn25}
|
||||||
if err = validate.IsOneOf(o.Version, validVersions...); err != nil {
|
if !helpers.IsOneOf(o.Version, validVersions...) {
|
||||||
return fmt.Errorf("%w: %w", ErrOpenVPNVersionIsNotValid, err)
|
return fmt.Errorf("%w: %q can only be one of %s",
|
||||||
|
ErrOpenVPNVersionIsNotValid, o.Version, strings.Join(validVersions, ", "))
|
||||||
}
|
}
|
||||||
|
|
||||||
isCustom := vpnProvider == providers.Custom
|
isCustom := vpnProvider == constants.Custom
|
||||||
isUserRequired := !isCustom &&
|
|
||||||
vpnProvider != providers.Airvpn &&
|
|
||||||
vpnProvider != providers.VPNSecure
|
|
||||||
|
|
||||||
if isUserRequired && *o.User == "" {
|
if !isCustom && o.User == "" {
|
||||||
return fmt.Errorf("%w", ErrOpenVPNUserIsEmpty)
|
return ErrOpenVPNUserIsEmpty
|
||||||
}
|
}
|
||||||
|
|
||||||
passwordRequired := isUserRequired &&
|
if !isCustom && o.Password == "" {
|
||||||
(vpnProvider != providers.Ivpn || !ivpnAccountID.MatchString(*o.User))
|
return ErrOpenVPNPasswordIsEmpty
|
||||||
|
|
||||||
if passwordRequired && *o.Password == "" {
|
|
||||||
return fmt.Errorf("%w", ErrOpenVPNPasswordIsEmpty)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err = validateOpenVPNConfigFilepath(isCustom, *o.ConfFile)
|
// Validate ConfFile
|
||||||
if err != nil {
|
if isCustom {
|
||||||
return fmt.Errorf("custom configuration file: %w", err)
|
if *o.ConfFile == "" {
|
||||||
|
return fmt.Errorf("%w: no file path specified", ErrOpenVPNConfigFile)
|
||||||
|
}
|
||||||
|
err := helpers.FileExists(*o.ConfFile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%w: %s", ErrOpenVPNConfigFile, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = validateOpenVPNClientCertificate(vpnProvider, *o.Cert)
|
// Check client certificate
|
||||||
if err != nil {
|
switch vpnProvider {
|
||||||
return fmt.Errorf("client certificate: %w", err)
|
case
|
||||||
|
constants.Cyberghost,
|
||||||
|
constants.VPNUnlimited:
|
||||||
|
if *o.ClientCrt == "" {
|
||||||
|
return ErrOpenVPNClientCertMissing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if *o.ClientCrt != "" {
|
||||||
|
_, err = parse.ExtractCert([]byte(*o.ClientCrt))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%w: %s", ErrOpenVPNClientCertNotValid, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = validateOpenVPNClientKey(vpnProvider, *o.Key)
|
// Check client key
|
||||||
if err != nil {
|
switch vpnProvider {
|
||||||
return fmt.Errorf("client key: %w", err)
|
case
|
||||||
}
|
constants.Cyberghost,
|
||||||
|
constants.VPNUnlimited,
|
||||||
err = validateOpenVPNEncryptedKey(vpnProvider, *o.EncryptedKey)
|
constants.Wevpn:
|
||||||
if err != nil {
|
if *o.ClientKey == "" {
|
||||||
return fmt.Errorf("encrypted key: %w", err)
|
return ErrOpenVPNClientKeyMissing
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if *o.EncryptedKey != "" && *o.KeyPassphrase == "" {
|
if *o.ClientKey != "" {
|
||||||
return fmt.Errorf("%w", ErrOpenVPNKeyPassphraseIsEmpty)
|
_, err = parse.ExtractPrivateKey([]byte(*o.ClientKey))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%w: %s", ErrOpenVPNClientKeyNotValid, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate MSSFix
|
||||||
const maxMSSFix = 10000
|
const maxMSSFix = 10000
|
||||||
if *o.MSSFix > maxMSSFix {
|
if *o.MSSFix > maxMSSFix {
|
||||||
return fmt.Errorf("%w: %d is over the maximum value of %d",
|
return fmt.Errorf("%w: %d is over the maximum value of %d",
|
||||||
@@ -144,6 +152,7 @@ func (o OpenVPN) validate(vpnProvider string) (err error) {
|
|||||||
ErrOpenVPNInterfaceNotValid, o.Interface, regexpInterfaceName)
|
ErrOpenVPNInterfaceNotValid, o.Interface, regexpInterfaceName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate Verbosity
|
||||||
if *o.Verbosity < 0 || *o.Verbosity > 6 {
|
if *o.Verbosity < 0 || *o.Verbosity > 6 {
|
||||||
return fmt.Errorf("%w: %d can only be between 0 and 5",
|
return fmt.Errorf("%w: %d can only be between 0 and 5",
|
||||||
ErrOpenVPNVerbosityIsOutOfBounds, o.Verbosity)
|
ErrOpenVPNVerbosityIsOutOfBounds, o.Verbosity)
|
||||||
@@ -152,183 +161,93 @@ func (o OpenVPN) validate(vpnProvider string) (err error) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateOpenVPNConfigFilepath(isCustom bool,
|
|
||||||
confFile string) (err error) {
|
|
||||||
if !isCustom {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if confFile == "" {
|
|
||||||
return fmt.Errorf("%w", ErrFilepathMissing)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = validate.FileExists(confFile)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
extractor := extract.New()
|
|
||||||
_, _, err = extractor.Data(confFile)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("extracting information from custom configuration file: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func validateOpenVPNClientCertificate(vpnProvider,
|
|
||||||
clientCert string) (err error) {
|
|
||||||
switch vpnProvider {
|
|
||||||
case
|
|
||||||
providers.Airvpn,
|
|
||||||
providers.Cyberghost,
|
|
||||||
providers.VPNSecure,
|
|
||||||
providers.VPNUnlimited:
|
|
||||||
if clientCert == "" {
|
|
||||||
return fmt.Errorf("%w", ErrMissingValue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if clientCert == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = base64.StdEncoding.DecodeString(clientCert)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func validateOpenVPNClientKey(vpnProvider, clientKey string) (err error) {
|
|
||||||
switch vpnProvider {
|
|
||||||
case
|
|
||||||
providers.Airvpn,
|
|
||||||
providers.Cyberghost,
|
|
||||||
providers.VPNUnlimited,
|
|
||||||
providers.Wevpn:
|
|
||||||
if clientKey == "" {
|
|
||||||
return fmt.Errorf("%w", ErrMissingValue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if clientKey == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = base64.StdEncoding.DecodeString(clientKey)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func validateOpenVPNEncryptedKey(vpnProvider,
|
|
||||||
encryptedPrivateKey string) (err error) {
|
|
||||||
if vpnProvider == providers.VPNSecure && encryptedPrivateKey == "" {
|
|
||||||
return fmt.Errorf("%w", ErrMissingValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
if encryptedPrivateKey == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = base64.StdEncoding.DecodeString(encryptedPrivateKey)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *OpenVPN) copy() (copied OpenVPN) {
|
func (o *OpenVPN) copy() (copied OpenVPN) {
|
||||||
return OpenVPN{
|
return OpenVPN{
|
||||||
Version: o.Version,
|
Version: o.Version,
|
||||||
User: gosettings.CopyPointer(o.User),
|
User: o.User,
|
||||||
Password: gosettings.CopyPointer(o.Password),
|
Password: o.Password,
|
||||||
ConfFile: gosettings.CopyPointer(o.ConfFile),
|
ConfFile: helpers.CopyStringPtr(o.ConfFile),
|
||||||
Ciphers: gosettings.CopySlice(o.Ciphers),
|
Ciphers: helpers.CopyStringSlice(o.Ciphers),
|
||||||
Auth: gosettings.CopyPointer(o.Auth),
|
Auth: helpers.CopyStringPtr(o.Auth),
|
||||||
Cert: gosettings.CopyPointer(o.Cert),
|
ClientCrt: helpers.CopyStringPtr(o.ClientCrt),
|
||||||
Key: gosettings.CopyPointer(o.Key),
|
ClientKey: helpers.CopyStringPtr(o.ClientKey),
|
||||||
EncryptedKey: gosettings.CopyPointer(o.EncryptedKey),
|
PIAEncPreset: helpers.CopyStringPtr(o.PIAEncPreset),
|
||||||
KeyPassphrase: gosettings.CopyPointer(o.KeyPassphrase),
|
IPv6: helpers.CopyBoolPtr(o.IPv6),
|
||||||
PIAEncPreset: gosettings.CopyPointer(o.PIAEncPreset),
|
MSSFix: helpers.CopyUint16Ptr(o.MSSFix),
|
||||||
MSSFix: gosettings.CopyPointer(o.MSSFix),
|
Interface: o.Interface,
|
||||||
Interface: o.Interface,
|
Root: helpers.CopyBoolPtr(o.Root),
|
||||||
ProcessUser: o.ProcessUser,
|
ProcUser: o.ProcUser,
|
||||||
Verbosity: gosettings.CopyPointer(o.Verbosity),
|
Verbosity: helpers.CopyIntPtr(o.Verbosity),
|
||||||
Flags: gosettings.CopySlice(o.Flags),
|
Flags: helpers.CopyStringSlice(o.Flags),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// mergeWith merges the other settings into any
|
// mergeWith merges the other settings into any
|
||||||
// unset field of the receiver settings object.
|
// unset field of the receiver settings object.
|
||||||
func (o *OpenVPN) mergeWith(other OpenVPN) {
|
func (o *OpenVPN) mergeWith(other OpenVPN) {
|
||||||
o.Version = gosettings.MergeWithString(o.Version, other.Version)
|
o.Version = helpers.MergeWithString(o.Version, other.Version)
|
||||||
o.User = gosettings.MergeWithPointer(o.User, other.User)
|
o.User = helpers.MergeWithString(o.User, other.User)
|
||||||
o.Password = gosettings.MergeWithPointer(o.Password, other.Password)
|
o.Password = helpers.MergeWithString(o.Password, other.Password)
|
||||||
o.ConfFile = gosettings.MergeWithPointer(o.ConfFile, other.ConfFile)
|
o.ConfFile = helpers.MergeWithStringPtr(o.ConfFile, other.ConfFile)
|
||||||
o.Ciphers = gosettings.MergeWithSlice(o.Ciphers, other.Ciphers)
|
o.Ciphers = helpers.MergeStringSlices(o.Ciphers, other.Ciphers)
|
||||||
o.Auth = gosettings.MergeWithPointer(o.Auth, other.Auth)
|
o.Auth = helpers.MergeWithStringPtr(o.Auth, other.Auth)
|
||||||
o.Cert = gosettings.MergeWithPointer(o.Cert, other.Cert)
|
o.ClientCrt = helpers.MergeWithStringPtr(o.ClientCrt, other.ClientCrt)
|
||||||
o.Key = gosettings.MergeWithPointer(o.Key, other.Key)
|
o.ClientKey = helpers.MergeWithStringPtr(o.ClientKey, other.ClientKey)
|
||||||
o.EncryptedKey = gosettings.MergeWithPointer(o.EncryptedKey, other.EncryptedKey)
|
o.PIAEncPreset = helpers.MergeWithStringPtr(o.PIAEncPreset, other.PIAEncPreset)
|
||||||
o.KeyPassphrase = gosettings.MergeWithPointer(o.KeyPassphrase, other.KeyPassphrase)
|
o.IPv6 = helpers.MergeWithBool(o.IPv6, other.IPv6)
|
||||||
o.PIAEncPreset = gosettings.MergeWithPointer(o.PIAEncPreset, other.PIAEncPreset)
|
o.MSSFix = helpers.MergeWithUint16(o.MSSFix, other.MSSFix)
|
||||||
o.MSSFix = gosettings.MergeWithPointer(o.MSSFix, other.MSSFix)
|
o.Interface = helpers.MergeWithString(o.Interface, other.Interface)
|
||||||
o.Interface = gosettings.MergeWithString(o.Interface, other.Interface)
|
o.Root = helpers.MergeWithBool(o.Root, other.Root)
|
||||||
o.ProcessUser = gosettings.MergeWithString(o.ProcessUser, other.ProcessUser)
|
o.ProcUser = helpers.MergeWithString(o.ProcUser, other.ProcUser)
|
||||||
o.Verbosity = gosettings.MergeWithPointer(o.Verbosity, other.Verbosity)
|
o.Verbosity = helpers.MergeWithInt(o.Verbosity, other.Verbosity)
|
||||||
o.Flags = gosettings.MergeWithSlice(o.Flags, other.Flags)
|
o.Flags = helpers.MergeStringSlices(o.Flags, other.Flags)
|
||||||
}
|
}
|
||||||
|
|
||||||
// overrideWith overrides fields of the receiver
|
// overrideWith overrides fields of the receiver
|
||||||
// settings object with any field set in the other
|
// settings object with any field set in the other
|
||||||
// settings.
|
// settings.
|
||||||
func (o *OpenVPN) overrideWith(other OpenVPN) {
|
func (o *OpenVPN) overrideWith(other OpenVPN) {
|
||||||
o.Version = gosettings.OverrideWithString(o.Version, other.Version)
|
o.Version = helpers.OverrideWithString(o.Version, other.Version)
|
||||||
o.User = gosettings.OverrideWithPointer(o.User, other.User)
|
o.User = helpers.OverrideWithString(o.User, other.User)
|
||||||
o.Password = gosettings.OverrideWithPointer(o.Password, other.Password)
|
o.Password = helpers.OverrideWithString(o.Password, other.Password)
|
||||||
o.ConfFile = gosettings.OverrideWithPointer(o.ConfFile, other.ConfFile)
|
o.ConfFile = helpers.OverrideWithStringPtr(o.ConfFile, other.ConfFile)
|
||||||
o.Ciphers = gosettings.OverrideWithSlice(o.Ciphers, other.Ciphers)
|
o.Ciphers = helpers.OverrideWithStringSlice(o.Ciphers, other.Ciphers)
|
||||||
o.Auth = gosettings.OverrideWithPointer(o.Auth, other.Auth)
|
o.Auth = helpers.OverrideWithStringPtr(o.Auth, other.Auth)
|
||||||
o.Cert = gosettings.OverrideWithPointer(o.Cert, other.Cert)
|
o.ClientCrt = helpers.OverrideWithStringPtr(o.ClientCrt, other.ClientCrt)
|
||||||
o.Key = gosettings.OverrideWithPointer(o.Key, other.Key)
|
o.ClientKey = helpers.OverrideWithStringPtr(o.ClientKey, other.ClientKey)
|
||||||
o.EncryptedKey = gosettings.OverrideWithPointer(o.EncryptedKey, other.EncryptedKey)
|
o.PIAEncPreset = helpers.OverrideWithStringPtr(o.PIAEncPreset, other.PIAEncPreset)
|
||||||
o.KeyPassphrase = gosettings.OverrideWithPointer(o.KeyPassphrase, other.KeyPassphrase)
|
o.IPv6 = helpers.OverrideWithBool(o.IPv6, other.IPv6)
|
||||||
o.PIAEncPreset = gosettings.OverrideWithPointer(o.PIAEncPreset, other.PIAEncPreset)
|
o.MSSFix = helpers.OverrideWithUint16(o.MSSFix, other.MSSFix)
|
||||||
o.MSSFix = gosettings.OverrideWithPointer(o.MSSFix, other.MSSFix)
|
o.Interface = helpers.OverrideWithString(o.Interface, other.Interface)
|
||||||
o.Interface = gosettings.OverrideWithString(o.Interface, other.Interface)
|
o.Root = helpers.OverrideWithBool(o.Root, other.Root)
|
||||||
o.ProcessUser = gosettings.OverrideWithString(o.ProcessUser, other.ProcessUser)
|
o.ProcUser = helpers.OverrideWithString(o.ProcUser, other.ProcUser)
|
||||||
o.Verbosity = gosettings.OverrideWithPointer(o.Verbosity, other.Verbosity)
|
o.Verbosity = helpers.OverrideWithInt(o.Verbosity, other.Verbosity)
|
||||||
o.Flags = gosettings.OverrideWithSlice(o.Flags, other.Flags)
|
o.Flags = helpers.OverrideWithStringSlice(o.Flags, other.Flags)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *OpenVPN) setDefaults(vpnProvider string) {
|
func (o *OpenVPN) setDefaults(vpnProvider string) {
|
||||||
o.Version = gosettings.DefaultString(o.Version, openvpn.Openvpn25)
|
o.Version = helpers.DefaultString(o.Version, constants.Openvpn25)
|
||||||
o.User = gosettings.DefaultPointer(o.User, "")
|
if vpnProvider == constants.Mullvad {
|
||||||
if vpnProvider == providers.Mullvad {
|
o.Password = "m"
|
||||||
o.Password = gosettings.DefaultPointer(o.Password, "m")
|
|
||||||
} else {
|
|
||||||
o.Password = gosettings.DefaultPointer(o.Password, "")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
o.ConfFile = gosettings.DefaultPointer(o.ConfFile, "")
|
o.ConfFile = helpers.DefaultStringPtr(o.ConfFile, "")
|
||||||
o.Auth = gosettings.DefaultPointer(o.Auth, "")
|
o.Auth = helpers.DefaultStringPtr(o.Auth, "")
|
||||||
o.Cert = gosettings.DefaultPointer(o.Cert, "")
|
o.ClientCrt = helpers.DefaultStringPtr(o.ClientCrt, "")
|
||||||
o.Key = gosettings.DefaultPointer(o.Key, "")
|
o.ClientKey = helpers.DefaultStringPtr(o.ClientKey, "")
|
||||||
o.EncryptedKey = gosettings.DefaultPointer(o.EncryptedKey, "")
|
|
||||||
o.KeyPassphrase = gosettings.DefaultPointer(o.KeyPassphrase, "")
|
|
||||||
|
|
||||||
var defaultEncPreset string
|
var defaultEncPreset string
|
||||||
if vpnProvider == providers.PrivateInternetAccess {
|
if vpnProvider == constants.PrivateInternetAccess {
|
||||||
defaultEncPreset = presets.Strong
|
defaultEncPreset = constants.PIAEncryptionPresetStrong
|
||||||
}
|
}
|
||||||
o.PIAEncPreset = gosettings.DefaultPointer(o.PIAEncPreset, defaultEncPreset)
|
o.PIAEncPreset = helpers.DefaultStringPtr(o.PIAEncPreset, defaultEncPreset)
|
||||||
o.MSSFix = gosettings.DefaultPointer(o.MSSFix, 0)
|
|
||||||
o.Interface = gosettings.DefaultString(o.Interface, "tun0")
|
o.IPv6 = helpers.DefaultBool(o.IPv6, false)
|
||||||
o.ProcessUser = gosettings.DefaultString(o.ProcessUser, "root")
|
o.MSSFix = helpers.DefaultUint16(o.MSSFix, 0)
|
||||||
o.Verbosity = gosettings.DefaultPointer(o.Verbosity, 1)
|
o.Interface = helpers.DefaultString(o.Interface, "tun0")
|
||||||
|
o.Root = helpers.DefaultBool(o.Root, true)
|
||||||
|
o.ProcUser = helpers.DefaultString(o.ProcUser, "root")
|
||||||
|
o.Verbosity = helpers.DefaultInt(o.Verbosity, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o OpenVPN) String() string {
|
func (o OpenVPN) String() string {
|
||||||
@@ -338,8 +257,8 @@ func (o OpenVPN) String() string {
|
|||||||
func (o OpenVPN) toLinesNode() (node *gotree.Node) {
|
func (o OpenVPN) toLinesNode() (node *gotree.Node) {
|
||||||
node = gotree.New("OpenVPN settings:")
|
node = gotree.New("OpenVPN settings:")
|
||||||
node.Appendf("OpenVPN version: %s", o.Version)
|
node.Appendf("OpenVPN version: %s", o.Version)
|
||||||
node.Appendf("User: %s", gosettings.ObfuscateKey(*o.User))
|
node.Appendf("User: %s", helpers.ObfuscatePassword(o.User))
|
||||||
node.Appendf("Password: %s", gosettings.ObfuscateKey(*o.Password))
|
node.Appendf("Password: %s", helpers.ObfuscatePassword(o.Password))
|
||||||
|
|
||||||
if *o.ConfFile != "" {
|
if *o.ConfFile != "" {
|
||||||
node.Appendf("Custom configuration file: %s", *o.ConfFile)
|
node.Appendf("Custom configuration file: %s", *o.ConfFile)
|
||||||
@@ -353,23 +272,20 @@ func (o OpenVPN) toLinesNode() (node *gotree.Node) {
|
|||||||
node.Appendf("Auth: %s", *o.Auth)
|
node.Appendf("Auth: %s", *o.Auth)
|
||||||
}
|
}
|
||||||
|
|
||||||
if *o.Cert != "" {
|
if *o.ClientCrt != "" {
|
||||||
node.Appendf("Client crt: %s", gosettings.ObfuscateKey(*o.Cert))
|
node.Appendf("Client crt: %s", helpers.ObfuscateData(*o.ClientCrt))
|
||||||
}
|
}
|
||||||
|
|
||||||
if *o.Key != "" {
|
if *o.ClientKey != "" {
|
||||||
node.Appendf("Client key: %s", gosettings.ObfuscateKey(*o.Key))
|
node.Appendf("Client key: %s", helpers.ObfuscateData(*o.ClientKey))
|
||||||
}
|
|
||||||
|
|
||||||
if *o.EncryptedKey != "" {
|
|
||||||
node.Appendf("Encrypted key: %s (key passhrapse %s)",
|
|
||||||
gosettings.ObfuscateKey(*o.EncryptedKey), gosettings.ObfuscateKey(*o.KeyPassphrase))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if *o.PIAEncPreset != "" {
|
if *o.PIAEncPreset != "" {
|
||||||
node.Appendf("Private Internet Access encryption preset: %s", *o.PIAEncPreset)
|
node.Appendf("Private Internet Access encryption preset: %s", *o.PIAEncPreset)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
node.Appendf("Tunnel IPv6: %s", helpers.BoolPtrToYesNo(o.IPv6))
|
||||||
|
|
||||||
if *o.MSSFix > 0 {
|
if *o.MSSFix > 0 {
|
||||||
node.Appendf("MSS Fix: %d", *o.MSSFix)
|
node.Appendf("MSS Fix: %d", *o.MSSFix)
|
||||||
}
|
}
|
||||||
@@ -378,7 +294,14 @@ func (o OpenVPN) toLinesNode() (node *gotree.Node) {
|
|||||||
node.Appendf("Network interface: %s", o.Interface)
|
node.Appendf("Network interface: %s", o.Interface)
|
||||||
}
|
}
|
||||||
|
|
||||||
node.Appendf("Run OpenVPN as: %s", o.ProcessUser)
|
processUser := "root"
|
||||||
|
if !*o.Root {
|
||||||
|
processUser = "some non root user" // TODO
|
||||||
|
if o.ProcUser != "" {
|
||||||
|
processUser = o.ProcUser
|
||||||
|
}
|
||||||
|
}
|
||||||
|
node.Appendf("Run OpenVPN as: %s", processUser)
|
||||||
|
|
||||||
node.Appendf("Verbosity level: %d", *o.Verbosity)
|
node.Appendf("Verbosity level: %d", *o.Verbosity)
|
||||||
|
|
||||||
|
|||||||
@@ -1,44 +0,0 @@
|
|||||||
package settings
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Test_ivpnAccountID(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
testCases := []struct {
|
|
||||||
s string
|
|
||||||
match bool
|
|
||||||
}{
|
|
||||||
{},
|
|
||||||
{s: "abc"},
|
|
||||||
{s: "i"},
|
|
||||||
{s: "ivpn"},
|
|
||||||
{s: "ivpn-aaaa"},
|
|
||||||
{s: "ivpn-aaaa-aaaa"},
|
|
||||||
{s: "ivpn-aaaa-aaaa-aaa"},
|
|
||||||
{s: "ivpn-aaaa-aaaa-aaaa", match: true},
|
|
||||||
{s: "ivpn-aaaa-aaaa-aaaaa"},
|
|
||||||
{s: "ivpn-a6B7-fP91-Zh6Y", match: true},
|
|
||||||
{s: "i-aaaa"},
|
|
||||||
{s: "i-aaaa-aaaa"},
|
|
||||||
{s: "i-aaaa-aaaa-aaa"},
|
|
||||||
{s: "i-aaaa-aaaa-aaaa", match: true},
|
|
||||||
{s: "i-aaaa-aaaa-aaaaa"},
|
|
||||||
{s: "i-a6B7-fP91-Zh6Y", match: true},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, testCase := range testCases {
|
|
||||||
testCase := testCase
|
|
||||||
t.Run(testCase.s, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
match := ivpnAccountID.MatchString(testCase.s)
|
|
||||||
|
|
||||||
assert.Equal(t, testCase.match, match)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,10 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
"github.com/qdm12/gluetun/internal/provider/privateinternetaccess/presets"
|
|
||||||
"github.com/qdm12/gosettings"
|
|
||||||
"github.com/qdm12/gosettings/validate"
|
|
||||||
"github.com/qdm12/gotree"
|
"github.com/qdm12/gotree"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -16,37 +13,35 @@ type OpenVPNSelection struct {
|
|||||||
// It can be set to an empty string to indicate to
|
// It can be set to an empty string to indicate to
|
||||||
// NOT use a custom configuration file.
|
// NOT use a custom configuration file.
|
||||||
// It cannot be nil in the internal state.
|
// It cannot be nil in the internal state.
|
||||||
ConfFile *string `json:"config_file_path"`
|
ConfFile *string
|
||||||
// TCP is true if the OpenVPN protocol is TCP,
|
// TCP is true if the OpenVPN protocol is TCP,
|
||||||
// and false for UDP.
|
// and false for UDP.
|
||||||
// It cannot be nil in the internal state.
|
// It cannot be nil in the internal state.
|
||||||
TCP *bool `json:"tcp"`
|
TCP *bool
|
||||||
// CustomPort is the OpenVPN server endpoint port.
|
// CustomPort is the OpenVPN server endpoint port.
|
||||||
// It can be set to 0 to indicate no custom port should
|
// It can be set to 0 to indicate no custom port should
|
||||||
// be used. It cannot be nil in the internal state.
|
// be used. It cannot be nil in the internal state.
|
||||||
CustomPort *uint16 `json:"custom_port"`
|
CustomPort *uint16 // HideMyAss, Mullvad, PIA, ProtonVPN, WeVPN, Windscribe
|
||||||
// PIAEncPreset is the encryption preset for
|
// PIAEncPreset is the encryption preset for
|
||||||
// Private Internet Access. It can be set to an
|
// Private Internet Access. It can be set to an
|
||||||
// empty string for other providers.
|
// empty string for other providers.
|
||||||
PIAEncPreset *string `json:"pia_encryption_preset"`
|
PIAEncPreset *string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
|
func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
|
||||||
// Validate ConfFile
|
// Validate ConfFile
|
||||||
if confFile := *o.ConfFile; confFile != "" {
|
if confFile := *o.ConfFile; confFile != "" {
|
||||||
err := validate.FileExists(confFile)
|
err := helpers.FileExists(confFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("configuration file: %w", err)
|
return fmt.Errorf("%w: %s", ErrOpenVPNConfigFile, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate TCP
|
// Validate TCP
|
||||||
if *o.TCP && helpers.IsOneOf(vpnProvider,
|
if *o.TCP && helpers.IsOneOf(vpnProvider,
|
||||||
providers.Ipvanish,
|
constants.Perfectprivacy,
|
||||||
providers.Perfectprivacy,
|
constants.Privado,
|
||||||
providers.Privado,
|
constants.Vyprvpn,
|
||||||
providers.VPNUnlimited,
|
|
||||||
providers.Vyprvpn,
|
|
||||||
) {
|
) {
|
||||||
return fmt.Errorf("%w: for VPN service provider %s",
|
return fmt.Errorf("%w: for VPN service provider %s",
|
||||||
ErrOpenVPNTCPNotSupported, vpnProvider)
|
ErrOpenVPNTCPNotSupported, vpnProvider)
|
||||||
@@ -56,74 +51,60 @@ func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
|
|||||||
if *o.CustomPort != 0 {
|
if *o.CustomPort != 0 {
|
||||||
switch vpnProvider {
|
switch vpnProvider {
|
||||||
// no restriction on port
|
// no restriction on port
|
||||||
case providers.Custom, providers.Cyberghost, providers.HideMyAss,
|
case constants.Cyberghost, constants.HideMyAss,
|
||||||
providers.Privatevpn, providers.Torguard:
|
constants.PrivateInternetAccess, constants.Privatevpn,
|
||||||
|
constants.Protonvpn, constants.Torguard:
|
||||||
// no custom port allowed
|
// no custom port allowed
|
||||||
case providers.Expressvpn, providers.Fastestvpn,
|
case constants.Expressvpn, constants.Fastestvpn,
|
||||||
providers.Ipvanish, providers.Nordvpn,
|
constants.Ipvanish, constants.Nordvpn,
|
||||||
providers.Privado, providers.Purevpn,
|
constants.Privado, constants.Purevpn,
|
||||||
providers.Surfshark, providers.VPNSecure,
|
constants.Surfshark, constants.VPNUnlimited,
|
||||||
providers.VPNUnlimited, providers.Vyprvpn:
|
constants.Vyprvpn:
|
||||||
return fmt.Errorf("%w: for VPN service provider %s",
|
return fmt.Errorf("%w: for VPN service provider %s",
|
||||||
ErrOpenVPNCustomPortNotAllowed, vpnProvider)
|
ErrOpenVPNCustomPortNotAllowed, vpnProvider)
|
||||||
default:
|
default:
|
||||||
var allowedTCP, allowedUDP []uint16
|
var allowedTCP, allowedUDP []uint16
|
||||||
switch vpnProvider {
|
switch vpnProvider {
|
||||||
case providers.Airvpn:
|
case constants.Ivpn:
|
||||||
allowedTCP = []uint16{
|
|
||||||
53, 80, 443, // IP in 1, 3
|
|
||||||
1194, 2018, 41185, // IP in 1, 2, 3, 4
|
|
||||||
}
|
|
||||||
allowedUDP = []uint16{53, 80, 443, 1194, 2018, 41185}
|
|
||||||
case providers.Ivpn:
|
|
||||||
allowedTCP = []uint16{80, 443, 1143}
|
allowedTCP = []uint16{80, 443, 1143}
|
||||||
allowedUDP = []uint16{53, 1194, 2049, 2050}
|
allowedUDP = []uint16{53, 1194, 2049, 2050}
|
||||||
case providers.Mullvad:
|
case constants.Mullvad:
|
||||||
allowedTCP = []uint16{80, 443, 1401}
|
allowedTCP = []uint16{80, 443, 1401}
|
||||||
allowedUDP = []uint16{53, 1194, 1195, 1196, 1197, 1300, 1301, 1302, 1303, 1400}
|
allowedUDP = []uint16{53, 1194, 1195, 1196, 1197, 1300, 1301, 1302, 1303, 1400}
|
||||||
case providers.Perfectprivacy:
|
case constants.Perfectprivacy:
|
||||||
allowedTCP = []uint16{44, 443, 4433}
|
allowedTCP = []uint16{44, 443, 4433}
|
||||||
allowedUDP = []uint16{44, 443, 4433}
|
allowedUDP = []uint16{44, 443, 4433}
|
||||||
case providers.PrivateInternetAccess:
|
case constants.Wevpn:
|
||||||
allowedTCP = []uint16{80, 110, 443}
|
|
||||||
allowedUDP = []uint16{53, 1194, 1197, 1198, 8080, 9201}
|
|
||||||
case providers.Protonvpn:
|
|
||||||
allowedTCP = []uint16{443, 5995, 8443}
|
|
||||||
allowedUDP = []uint16{80, 443, 1194, 4569, 5060}
|
|
||||||
case providers.SlickVPN:
|
|
||||||
allowedTCP = []uint16{443, 8080, 8888}
|
|
||||||
allowedUDP = []uint16{443, 8080, 8888}
|
|
||||||
case providers.Wevpn:
|
|
||||||
allowedTCP = []uint16{53, 1195, 1199, 2018}
|
allowedTCP = []uint16{53, 1195, 1199, 2018}
|
||||||
allowedUDP = []uint16{80, 1194, 1198}
|
allowedUDP = []uint16{80, 1194, 1198}
|
||||||
case providers.Windscribe:
|
case constants.Windscribe:
|
||||||
allowedTCP = []uint16{21, 22, 80, 123, 143, 443, 587, 1194, 3306, 8080, 54783}
|
allowedTCP = []uint16{21, 22, 80, 123, 143, 443, 587, 1194, 3306, 8080, 54783}
|
||||||
allowedUDP = []uint16{53, 80, 123, 443, 1194, 54783}
|
allowedUDP = []uint16{53, 80, 123, 443, 1194, 54783}
|
||||||
default:
|
|
||||||
panic(fmt.Sprintf("VPN provider %s has no registered allowed ports", vpnProvider))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
allowedPorts := allowedUDP
|
if *o.TCP && !helpers.Uint16IsOneOf(*o.CustomPort, allowedTCP) {
|
||||||
if *o.TCP {
|
return fmt.Errorf("%w: %d for VPN service provider %s; %s",
|
||||||
allowedPorts = allowedTCP
|
ErrOpenVPNCustomPortNotAllowed, o.CustomPort, vpnProvider,
|
||||||
}
|
helpers.PortChoicesOrString(allowedTCP))
|
||||||
err = validate.IsOneOf(*o.CustomPort, allowedPorts...)
|
} else if !*o.TCP && !helpers.Uint16IsOneOf(*o.CustomPort, allowedUDP) {
|
||||||
if err != nil {
|
return fmt.Errorf("%w: %d for VPN service provider %s; %s",
|
||||||
return fmt.Errorf("%w: for VPN service provider %s: %w",
|
ErrOpenVPNCustomPortNotAllowed, o.CustomPort, vpnProvider,
|
||||||
ErrOpenVPNCustomPortNotAllowed, vpnProvider, err)
|
helpers.PortChoicesOrString(allowedUDP))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate EncPreset
|
// Validate EncPreset
|
||||||
if vpnProvider == providers.PrivateInternetAccess {
|
if vpnProvider == constants.PrivateInternetAccess {
|
||||||
validEncryptionPresets := []string{
|
validEncryptionPresets := []string{
|
||||||
presets.None,
|
constants.PIAEncryptionPresetNone,
|
||||||
presets.Normal,
|
constants.PIAEncryptionPresetNormal,
|
||||||
presets.Strong,
|
constants.PIAEncryptionPresetStrong,
|
||||||
}
|
}
|
||||||
if err = validate.IsOneOf(*o.PIAEncPreset, validEncryptionPresets...); err != nil {
|
if !helpers.IsOneOf(*o.PIAEncPreset, validEncryptionPresets...) {
|
||||||
return fmt.Errorf("%w: %w", ErrOpenVPNEncryptionPresetNotValid, err)
|
return fmt.Errorf("%w: %s; valid presets are %s",
|
||||||
|
ErrOpenVPNEncryptionPresetNotValid, *o.PIAEncPreset,
|
||||||
|
helpers.ChoicesOrString(validEncryptionPresets))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,37 +113,37 @@ func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
|
|||||||
|
|
||||||
func (o *OpenVPNSelection) copy() (copied OpenVPNSelection) {
|
func (o *OpenVPNSelection) copy() (copied OpenVPNSelection) {
|
||||||
return OpenVPNSelection{
|
return OpenVPNSelection{
|
||||||
ConfFile: gosettings.CopyPointer(o.ConfFile),
|
ConfFile: helpers.CopyStringPtr(o.ConfFile),
|
||||||
TCP: gosettings.CopyPointer(o.TCP),
|
TCP: helpers.CopyBoolPtr(o.TCP),
|
||||||
CustomPort: gosettings.CopyPointer(o.CustomPort),
|
CustomPort: helpers.CopyUint16Ptr(o.CustomPort),
|
||||||
PIAEncPreset: gosettings.CopyPointer(o.PIAEncPreset),
|
PIAEncPreset: helpers.CopyStringPtr(o.PIAEncPreset),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *OpenVPNSelection) mergeWith(other OpenVPNSelection) {
|
func (o *OpenVPNSelection) mergeWith(other OpenVPNSelection) {
|
||||||
o.ConfFile = gosettings.MergeWithPointer(o.ConfFile, other.ConfFile)
|
o.ConfFile = helpers.MergeWithStringPtr(o.ConfFile, other.ConfFile)
|
||||||
o.TCP = gosettings.MergeWithPointer(o.TCP, other.TCP)
|
o.TCP = helpers.MergeWithBool(o.TCP, other.TCP)
|
||||||
o.CustomPort = gosettings.MergeWithPointer(o.CustomPort, other.CustomPort)
|
o.CustomPort = helpers.MergeWithUint16(o.CustomPort, other.CustomPort)
|
||||||
o.PIAEncPreset = gosettings.MergeWithPointer(o.PIAEncPreset, other.PIAEncPreset)
|
o.PIAEncPreset = helpers.MergeWithStringPtr(o.PIAEncPreset, other.PIAEncPreset)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *OpenVPNSelection) overrideWith(other OpenVPNSelection) {
|
func (o *OpenVPNSelection) overrideWith(other OpenVPNSelection) {
|
||||||
o.ConfFile = gosettings.OverrideWithPointer(o.ConfFile, other.ConfFile)
|
o.ConfFile = helpers.OverrideWithStringPtr(o.ConfFile, other.ConfFile)
|
||||||
o.TCP = gosettings.OverrideWithPointer(o.TCP, other.TCP)
|
o.TCP = helpers.OverrideWithBool(o.TCP, other.TCP)
|
||||||
o.CustomPort = gosettings.OverrideWithPointer(o.CustomPort, other.CustomPort)
|
o.CustomPort = helpers.OverrideWithUint16(o.CustomPort, other.CustomPort)
|
||||||
o.PIAEncPreset = gosettings.OverrideWithPointer(o.PIAEncPreset, other.PIAEncPreset)
|
o.PIAEncPreset = helpers.OverrideWithStringPtr(o.PIAEncPreset, other.PIAEncPreset)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *OpenVPNSelection) setDefaults(vpnProvider string) {
|
func (o *OpenVPNSelection) setDefaults(vpnProvider string) {
|
||||||
o.ConfFile = gosettings.DefaultPointer(o.ConfFile, "")
|
o.ConfFile = helpers.DefaultStringPtr(o.ConfFile, "")
|
||||||
o.TCP = gosettings.DefaultPointer(o.TCP, false)
|
o.TCP = helpers.DefaultBool(o.TCP, false)
|
||||||
o.CustomPort = gosettings.DefaultPointer(o.CustomPort, 0)
|
o.CustomPort = helpers.DefaultUint16(o.CustomPort, 0)
|
||||||
|
|
||||||
var defaultEncPreset string
|
var defaultEncPreset string
|
||||||
if vpnProvider == providers.PrivateInternetAccess {
|
if vpnProvider == constants.PrivateInternetAccess {
|
||||||
defaultEncPreset = presets.Strong
|
defaultEncPreset = constants.PIAEncryptionPresetStrong
|
||||||
}
|
}
|
||||||
o.PIAEncPreset = gosettings.DefaultPointer(o.PIAEncPreset, defaultEncPreset)
|
o.PIAEncPreset = helpers.DefaultStringPtr(o.PIAEncPreset, defaultEncPreset)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o OpenVPNSelection) String() string {
|
func (o OpenVPNSelection) String() string {
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ package settings
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||||
"github.com/qdm12/gosettings"
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
"github.com/qdm12/gosettings/validate"
|
|
||||||
"github.com/qdm12/gotree"
|
"github.com/qdm12/gotree"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -14,83 +14,57 @@ import (
|
|||||||
type PortForwarding struct {
|
type PortForwarding struct {
|
||||||
// Enabled is true if port forwarding should be activated.
|
// Enabled is true if port forwarding should be activated.
|
||||||
// It cannot be nil for the internal state.
|
// It cannot be nil for the internal state.
|
||||||
Enabled *bool `json:"enabled"`
|
Enabled *bool
|
||||||
// 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
|
// Filepath is the port forwarding status file path
|
||||||
// to use. It can be the empty string to indicate not
|
// to use. It can be the empty string to indicate not
|
||||||
// to write to a file. It cannot be nil for the
|
// to write to a file. It cannot be nil for the
|
||||||
// internal state
|
// internal state
|
||||||
Filepath *string `json:"status_file_path"`
|
Filepath *string
|
||||||
// ListeningPort is the port traffic would be redirected to from the
|
|
||||||
// forwarded port. The redirection is disabled if it is set to 0, which
|
|
||||||
// is its default as well.
|
|
||||||
ListeningPort *uint16 `json:"listening_port"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p PortForwarding) Validate(vpnProvider string) (err error) {
|
func (p PortForwarding) validate(vpnProvider string) (err error) {
|
||||||
if !*p.Enabled {
|
if !*p.Enabled {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate current provider or custom provider specified
|
// Validate Enabled
|
||||||
providerSelected := vpnProvider
|
validProviders := []string{constants.PrivateInternetAccess}
|
||||||
if *p.Provider != "" {
|
if !helpers.IsOneOf(vpnProvider, validProviders...) {
|
||||||
providerSelected = *p.Provider
|
return fmt.Errorf("%w: for provider %s, it is only available for %s",
|
||||||
}
|
ErrPortForwardingEnabled, vpnProvider, strings.Join(validProviders, ", "))
|
||||||
validProviders := []string{
|
|
||||||
providers.PrivateInternetAccess,
|
|
||||||
providers.Protonvpn,
|
|
||||||
}
|
|
||||||
if err = validate.IsOneOf(providerSelected, validProviders...); err != nil {
|
|
||||||
return fmt.Errorf("%w: %w", ErrPortForwardingEnabled, err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate Filepath
|
// Validate Filepath
|
||||||
if *p.Filepath != "" { // optional
|
if *p.Filepath != "" { // optional
|
||||||
_, err := filepath.Abs(*p.Filepath)
|
_, err := filepath.Abs(*p.Filepath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("filepath is not valid: %w", err)
|
return fmt.Errorf("%w: %s", ErrPortForwardingFilepathNotValid, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *PortForwarding) Copy() (copied PortForwarding) {
|
func (p *PortForwarding) copy() (copied PortForwarding) {
|
||||||
return PortForwarding{
|
return PortForwarding{
|
||||||
Enabled: gosettings.CopyPointer(p.Enabled),
|
Enabled: helpers.CopyBoolPtr(p.Enabled),
|
||||||
Provider: gosettings.CopyPointer(p.Provider),
|
Filepath: helpers.CopyStringPtr(p.Filepath),
|
||||||
Filepath: gosettings.CopyPointer(p.Filepath),
|
|
||||||
ListeningPort: gosettings.CopyPointer(p.ListeningPort),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *PortForwarding) mergeWith(other PortForwarding) {
|
func (p *PortForwarding) mergeWith(other PortForwarding) {
|
||||||
p.Enabled = gosettings.MergeWithPointer(p.Enabled, other.Enabled)
|
p.Enabled = helpers.MergeWithBool(p.Enabled, other.Enabled)
|
||||||
p.Provider = gosettings.MergeWithPointer(p.Provider, other.Provider)
|
p.Filepath = helpers.MergeWithStringPtr(p.Filepath, other.Filepath)
|
||||||
p.Filepath = gosettings.MergeWithPointer(p.Filepath, other.Filepath)
|
|
||||||
p.ListeningPort = gosettings.MergeWithPointer(p.ListeningPort, other.ListeningPort)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *PortForwarding) OverrideWith(other PortForwarding) {
|
func (p *PortForwarding) overrideWith(other PortForwarding) {
|
||||||
p.Enabled = gosettings.OverrideWithPointer(p.Enabled, other.Enabled)
|
p.Enabled = helpers.OverrideWithBool(p.Enabled, other.Enabled)
|
||||||
p.Provider = gosettings.OverrideWithPointer(p.Provider, other.Provider)
|
p.Filepath = helpers.OverrideWithStringPtr(p.Filepath, other.Filepath)
|
||||||
p.Filepath = gosettings.OverrideWithPointer(p.Filepath, other.Filepath)
|
|
||||||
p.ListeningPort = gosettings.OverrideWithPointer(p.ListeningPort, other.ListeningPort)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *PortForwarding) setDefaults() {
|
func (p *PortForwarding) setDefaults() {
|
||||||
p.Enabled = gosettings.DefaultPointer(p.Enabled, false)
|
p.Enabled = helpers.DefaultBool(p.Enabled, false)
|
||||||
p.Provider = gosettings.DefaultPointer(p.Provider, "")
|
p.Filepath = helpers.DefaultStringPtr(p.Filepath, "/tmp/gluetun/forwarded_port")
|
||||||
p.Filepath = gosettings.DefaultPointer(p.Filepath, "/tmp/gluetun/forwarded_port")
|
|
||||||
p.ListeningPort = gosettings.DefaultPointer(p.ListeningPort, 0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p PortForwarding) String() string {
|
func (p PortForwarding) String() string {
|
||||||
@@ -103,18 +77,7 @@ func (p PortForwarding) toLinesNode() (node *gotree.Node) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
node = gotree.New("Automatic port forwarding settings:")
|
node = gotree.New("Automatic port forwarding settings:")
|
||||||
|
node.Appendf("Enabled: yes")
|
||||||
listeningPort := "disabled"
|
|
||||||
if *p.ListeningPort != 0 {
|
|
||||||
listeningPort = fmt.Sprintf("%d", *p.ListeningPort)
|
|
||||||
}
|
|
||||||
node.Appendf("Redirection listening port: %s", listeningPort)
|
|
||||||
|
|
||||||
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
|
filepath := *p.Filepath
|
||||||
if filepath == "" {
|
if filepath == "" {
|
||||||
|
|||||||
@@ -3,10 +3,9 @@ package settings
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||||
"github.com/qdm12/gluetun/internal/constants/vpn"
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
"github.com/qdm12/gosettings"
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
"github.com/qdm12/gosettings/validate"
|
|
||||||
"github.com/qdm12/gotree"
|
"github.com/qdm12/gotree"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -14,44 +13,42 @@ import (
|
|||||||
type Provider struct {
|
type Provider struct {
|
||||||
// Name is the VPN service provider name.
|
// Name is the VPN service provider name.
|
||||||
// It cannot be nil in the internal state.
|
// It cannot be nil in the internal state.
|
||||||
Name *string `json:"name"`
|
Name *string
|
||||||
// ServerSelection is the settings to
|
// ServerSelection is the settings to
|
||||||
// select the VPN server.
|
// select the VPN server.
|
||||||
ServerSelection ServerSelection `json:"server_selection"`
|
ServerSelection ServerSelection
|
||||||
// PortForwarding is the settings about port forwarding.
|
// PortForwarding is the settings about port forwarding.
|
||||||
PortForwarding PortForwarding `json:"port_forwarding"`
|
PortForwarding PortForwarding
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO v4 remove pointer for receiver (because of Surfshark).
|
// TODO v4 remove pointer for receiver (because of Surfshark).
|
||||||
func (p *Provider) validate(vpnType string, storage Storage) (err error) {
|
func (p *Provider) validate(vpnType string, allServers models.AllServers) (err error) {
|
||||||
// Validate Name
|
// Validate Name
|
||||||
var validNames []string
|
var validNames []string
|
||||||
if vpnType == vpn.OpenVPN {
|
if vpnType == constants.OpenVPN {
|
||||||
validNames = providers.AllWithCustom()
|
validNames = constants.AllProviders()
|
||||||
validNames = append(validNames, "pia") // Retro-compatibility
|
validNames = append(validNames, "pia") // Retro-compatibility
|
||||||
} else { // Wireguard
|
} else { // Wireguard
|
||||||
validNames = []string{
|
validNames = []string{
|
||||||
providers.Airvpn,
|
constants.Custom,
|
||||||
providers.Custom,
|
constants.Ivpn,
|
||||||
providers.Ivpn,
|
constants.Mullvad,
|
||||||
providers.Mullvad,
|
constants.Windscribe,
|
||||||
providers.Nordvpn,
|
|
||||||
providers.Surfshark,
|
|
||||||
providers.Windscribe,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err = validate.IsOneOf(*p.Name, validNames...); err != nil {
|
if !helpers.IsOneOf(*p.Name, validNames...) {
|
||||||
return fmt.Errorf("%w for Wireguard: %w", ErrVPNProviderNameNotValid, err)
|
return fmt.Errorf("%w: %q can only be one of %s",
|
||||||
|
ErrVPNProviderNameNotValid, *p.Name, helpers.ChoicesOrString(validNames))
|
||||||
}
|
}
|
||||||
|
|
||||||
err = p.ServerSelection.validate(*p.Name, storage)
|
err = p.ServerSelection.validate(*p.Name, allServers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("server selection: %w", err)
|
return fmt.Errorf("server selection settings validation failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = p.PortForwarding.Validate(*p.Name)
|
err = p.PortForwarding.validate(*p.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("port forwarding: %w", err)
|
return fmt.Errorf("port forwarding settings validation failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -59,26 +56,26 @@ func (p *Provider) validate(vpnType string, storage Storage) (err error) {
|
|||||||
|
|
||||||
func (p *Provider) copy() (copied Provider) {
|
func (p *Provider) copy() (copied Provider) {
|
||||||
return Provider{
|
return Provider{
|
||||||
Name: gosettings.CopyPointer(p.Name),
|
Name: helpers.CopyStringPtr(p.Name),
|
||||||
ServerSelection: p.ServerSelection.copy(),
|
ServerSelection: p.ServerSelection.copy(),
|
||||||
PortForwarding: p.PortForwarding.Copy(),
|
PortForwarding: p.PortForwarding.copy(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Provider) mergeWith(other Provider) {
|
func (p *Provider) mergeWith(other Provider) {
|
||||||
p.Name = gosettings.MergeWithPointer(p.Name, other.Name)
|
p.Name = helpers.MergeWithStringPtr(p.Name, other.Name)
|
||||||
p.ServerSelection.mergeWith(other.ServerSelection)
|
p.ServerSelection.mergeWith(other.ServerSelection)
|
||||||
p.PortForwarding.mergeWith(other.PortForwarding)
|
p.PortForwarding.mergeWith(other.PortForwarding)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Provider) overrideWith(other Provider) {
|
func (p *Provider) overrideWith(other Provider) {
|
||||||
p.Name = gosettings.OverrideWithPointer(p.Name, other.Name)
|
p.Name = helpers.OverrideWithStringPtr(p.Name, other.Name)
|
||||||
p.ServerSelection.overrideWith(other.ServerSelection)
|
p.ServerSelection.overrideWith(other.ServerSelection)
|
||||||
p.PortForwarding.OverrideWith(other.PortForwarding)
|
p.PortForwarding.overrideWith(other.PortForwarding)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Provider) setDefaults() {
|
func (p *Provider) setDefaults() {
|
||||||
p.Name = gosettings.DefaultPointer(p.Name, providers.PrivateInternetAccess)
|
p.Name = helpers.DefaultStringPtr(p.Name, constants.PrivateInternetAccess)
|
||||||
p.ServerSelection.setDefaults(*p.Name)
|
p.ServerSelection.setDefaults(*p.Name)
|
||||||
p.PortForwarding.setDefaults()
|
p.PortForwarding.setDefaults()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/publicip/api"
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||||
"github.com/qdm12/gosettings"
|
|
||||||
"github.com/qdm12/gotree"
|
"github.com/qdm12/gotree"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -22,28 +21,6 @@ type PublicIP struct {
|
|||||||
// to write to a file. It cannot be nil for the
|
// to write to a file. It cannot be nil for the
|
||||||
// internal state
|
// internal state
|
||||||
IPFilepath *string
|
IPFilepath *string
|
||||||
// API is the API name to use to fetch public IP information.
|
|
||||||
// It can be ipinfo or ip2location. It defaults to ipinfo.
|
|
||||||
API string
|
|
||||||
// APIToken is the token to use for the IP data service
|
|
||||||
// such as ipinfo.io. It can be the empty string to
|
|
||||||
// indicate not to use a token. It cannot be nil for the
|
|
||||||
// internal state.
|
|
||||||
APIToken *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) {
|
func (p PublicIP) validate() (err error) {
|
||||||
@@ -56,47 +33,34 @@ func (p PublicIP) validate() (err error) {
|
|||||||
if *p.IPFilepath != "" { // optional
|
if *p.IPFilepath != "" { // optional
|
||||||
_, err := filepath.Abs(*p.IPFilepath)
|
_, err := filepath.Abs(*p.IPFilepath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("filepath is not valid: %w", err)
|
return fmt.Errorf("%w: %s", ErrPublicIPFilepathNotValid, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = api.ParseProvider(p.API)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("API name: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *PublicIP) copy() (copied PublicIP) {
|
func (p *PublicIP) copy() (copied PublicIP) {
|
||||||
return PublicIP{
|
return PublicIP{
|
||||||
Period: gosettings.CopyPointer(p.Period),
|
Period: helpers.CopyDurationPtr(p.Period),
|
||||||
IPFilepath: gosettings.CopyPointer(p.IPFilepath),
|
IPFilepath: helpers.CopyStringPtr(p.IPFilepath),
|
||||||
API: p.API,
|
|
||||||
APIToken: gosettings.CopyPointer(p.APIToken),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *PublicIP) mergeWith(other PublicIP) {
|
func (p *PublicIP) mergeWith(other PublicIP) {
|
||||||
p.Period = gosettings.MergeWithPointer(p.Period, other.Period)
|
p.Period = helpers.MergeWithDuration(p.Period, other.Period)
|
||||||
p.IPFilepath = gosettings.MergeWithPointer(p.IPFilepath, other.IPFilepath)
|
p.IPFilepath = helpers.MergeWithStringPtr(p.IPFilepath, other.IPFilepath)
|
||||||
p.API = gosettings.MergeWithString(p.API, other.API)
|
|
||||||
p.APIToken = gosettings.MergeWithPointer(p.APIToken, other.APIToken)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *PublicIP) overrideWith(other PublicIP) {
|
func (p *PublicIP) overrideWith(other PublicIP) {
|
||||||
p.Period = gosettings.OverrideWithPointer(p.Period, other.Period)
|
p.Period = helpers.OverrideWithDuration(p.Period, other.Period)
|
||||||
p.IPFilepath = gosettings.OverrideWithPointer(p.IPFilepath, other.IPFilepath)
|
p.IPFilepath = helpers.OverrideWithStringPtr(p.IPFilepath, other.IPFilepath)
|
||||||
p.API = gosettings.OverrideWithString(p.API, other.API)
|
|
||||||
p.APIToken = gosettings.OverrideWithPointer(p.APIToken, other.APIToken)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *PublicIP) setDefaults() {
|
func (p *PublicIP) setDefaults() {
|
||||||
const defaultPeriod = 12 * time.Hour
|
const defaultPeriod = 12 * time.Hour
|
||||||
p.Period = gosettings.DefaultPointer(p.Period, defaultPeriod)
|
p.Period = helpers.DefaultDuration(p.Period, defaultPeriod)
|
||||||
p.IPFilepath = gosettings.DefaultPointer(p.IPFilepath, "/tmp/gluetun/ip")
|
p.IPFilepath = helpers.DefaultStringPtr(p.IPFilepath, "/tmp/gluetun/ip")
|
||||||
p.API = gosettings.DefaultString(p.API, "ipinfo")
|
|
||||||
p.APIToken = gosettings.DefaultPointer(p.APIToken, "")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p PublicIP) String() string {
|
func (p PublicIP) String() string {
|
||||||
@@ -121,11 +85,5 @@ func (p PublicIP) toLinesNode() (node *gotree.Node) {
|
|||||||
node.Appendf("IP file path: %s", *p.IPFilepath)
|
node.Appendf("IP file path: %s", *p.IPFilepath)
|
||||||
}
|
}
|
||||||
|
|
||||||
node.Appendf("Public IP data API: %s", p.API)
|
|
||||||
|
|
||||||
if *p.APIToken != "" {
|
|
||||||
node.Appendf("API token: %s", gosettings.ObfuscateKey(*p.APIToken))
|
|
||||||
}
|
|
||||||
|
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,40 +2,30 @@ package settings
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/qdm12/gosettings"
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||||
"github.com/qdm12/gotree"
|
"github.com/qdm12/gotree"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ControlServer contains settings to customize the control server operation.
|
// ControlServer contains settings to customize the control server operation.
|
||||||
type ControlServer struct {
|
type ControlServer struct {
|
||||||
// Address is the listening address to use.
|
// Port is the listening port to use.
|
||||||
|
// It can be set to 0 to bind to a random port.
|
||||||
// It cannot be nil in the internal state.
|
// It cannot be nil in the internal state.
|
||||||
Address *string
|
// TODO change to address
|
||||||
|
Port *uint16
|
||||||
// Log can be true or false to enable logging on requests.
|
// Log can be true or false to enable logging on requests.
|
||||||
// It cannot be nil in the internal state.
|
// It cannot be nil in the internal state.
|
||||||
Log *bool
|
Log *bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c ControlServer) validate() (err error) {
|
func (c ControlServer) validate() (err error) {
|
||||||
_, portStr, err := net.SplitHostPort(*c.Address)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("listening address is not valid: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
port, err := strconv.Atoi(portStr)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("listening port it not valid: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
uid := os.Getuid()
|
uid := os.Getuid()
|
||||||
const maxPrivilegedPort = 1023
|
const maxPrivilegedPort uint16 = 1023
|
||||||
if uid != 0 && port != 0 && port <= maxPrivilegedPort {
|
if uid != 0 && *c.Port <= maxPrivilegedPort {
|
||||||
return fmt.Errorf("%w: %d when running with user ID %d",
|
return fmt.Errorf("%w: %d when running with user ID %d",
|
||||||
ErrControlServerPrivilegedPort, port, uid)
|
ErrControlServerPrivilegedPort, *c.Port, uid)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -43,29 +33,30 @@ func (c ControlServer) validate() (err error) {
|
|||||||
|
|
||||||
func (c *ControlServer) copy() (copied ControlServer) {
|
func (c *ControlServer) copy() (copied ControlServer) {
|
||||||
return ControlServer{
|
return ControlServer{
|
||||||
Address: gosettings.CopyPointer(c.Address),
|
Port: helpers.CopyUint16Ptr(c.Port),
|
||||||
Log: gosettings.CopyPointer(c.Log),
|
Log: helpers.CopyBoolPtr(c.Log),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// mergeWith merges the other settings into any
|
// mergeWith merges the other settings into any
|
||||||
// unset field of the receiver settings object.
|
// unset field of the receiver settings object.
|
||||||
func (c *ControlServer) mergeWith(other ControlServer) {
|
func (c *ControlServer) mergeWith(other ControlServer) {
|
||||||
c.Address = gosettings.MergeWithPointer(c.Address, other.Address)
|
c.Port = helpers.MergeWithUint16(c.Port, other.Port)
|
||||||
c.Log = gosettings.MergeWithPointer(c.Log, other.Log)
|
c.Log = helpers.MergeWithBool(c.Log, other.Log)
|
||||||
}
|
}
|
||||||
|
|
||||||
// overrideWith overrides fields of the receiver
|
// overrideWith overrides fields of the receiver
|
||||||
// settings object with any field set in the other
|
// settings object with any field set in the other
|
||||||
// settings.
|
// settings.
|
||||||
func (c *ControlServer) overrideWith(other ControlServer) {
|
func (c *ControlServer) overrideWith(other ControlServer) {
|
||||||
c.Address = gosettings.OverrideWithPointer(c.Address, other.Address)
|
c.Port = helpers.MergeWithUint16(c.Port, other.Port)
|
||||||
c.Log = gosettings.OverrideWithPointer(c.Log, other.Log)
|
c.Log = helpers.MergeWithBool(c.Log, other.Log)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ControlServer) setDefaults() {
|
func (c *ControlServer) setDefaults() {
|
||||||
c.Address = gosettings.DefaultPointer(c.Address, ":8000")
|
const defaultPort = 8000
|
||||||
c.Log = gosettings.DefaultPointer(c.Log, true)
|
c.Port = helpers.DefaultUint16(c.Port, defaultPort)
|
||||||
|
c.Log = helpers.DefaultBool(c.Log, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c ControlServer) String() string {
|
func (c ControlServer) String() string {
|
||||||
@@ -74,7 +65,7 @@ func (c ControlServer) String() string {
|
|||||||
|
|
||||||
func (c ControlServer) toLinesNode() (node *gotree.Node) {
|
func (c ControlServer) toLinesNode() (node *gotree.Node) {
|
||||||
node = gotree.New("Control server settings:")
|
node = gotree.New("Control server settings:")
|
||||||
node.Appendf("Listening address: %s", *c.Address)
|
node.Appendf("Listening port: %d", *c.Port)
|
||||||
node.Appendf("Logging: %s", gosettings.BoolToYesNo(c.Log))
|
node.Appendf("Logging: %s", helpers.BoolPtrToYesNo(c.Log))
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,13 @@
|
|||||||
package settings
|
package settings
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/netip"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings/validation"
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
|
||||||
"github.com/qdm12/gluetun/internal/constants/vpn"
|
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
"github.com/qdm12/gosettings"
|
|
||||||
"github.com/qdm12/gosettings/validate"
|
|
||||||
"github.com/qdm12/gotree"
|
"github.com/qdm12/gotree"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -20,215 +15,232 @@ type ServerSelection struct { //nolint:maligned
|
|||||||
// VPN is the VPN type which can be 'openvpn'
|
// VPN is the VPN type which can be 'openvpn'
|
||||||
// or 'wireguard'. It cannot be the empty string
|
// or 'wireguard'. It cannot be the empty string
|
||||||
// in the internal state.
|
// in the internal state.
|
||||||
VPN string `json:"vpn"`
|
VPN string
|
||||||
// TargetIP is the server endpoint IP address to use.
|
// TargetIP is the server endpoint IP address to use.
|
||||||
// It will override any IP address from the picked
|
// It will override any IP address from the picked
|
||||||
// built-in server. It cannot be the empty value in the internal
|
// built-in server. It cannot be nil in the internal
|
||||||
// state, and can be set to the unspecified address to indicate
|
// state, and can be set to an empty net.IP{} to indicate
|
||||||
// there is not target IP address to use.
|
// there is not target IP address to use.
|
||||||
TargetIP netip.Addr `json:"target_ip"`
|
TargetIP net.IP
|
||||||
// Countries is the list of countries to filter VPN servers with.
|
// Counties is the list of countries to filter VPN servers with.
|
||||||
Countries []string `json:"countries"`
|
Countries []string
|
||||||
// Categories is the list of categories to filter VPN servers with.
|
|
||||||
Categories []string `json:"categories"`
|
|
||||||
// Regions is the list of regions to filter VPN servers with.
|
// Regions is the list of regions to filter VPN servers with.
|
||||||
Regions []string `json:"regions"`
|
Regions []string
|
||||||
// Cities is the list of cities to filter VPN servers with.
|
// Cities is the list of cities to filter VPN servers with.
|
||||||
Cities []string `json:"cities"`
|
Cities []string
|
||||||
// ISPs is the list of ISP names to filter VPN servers with.
|
// ISPs is the list of ISP names to filter VPN servers with.
|
||||||
ISPs []string `json:"isps"`
|
ISPs []string
|
||||||
// Names is the list of server names to filter VPN servers with.
|
// Names is the list of server names to filter VPN servers with.
|
||||||
Names []string `json:"names"`
|
Names []string
|
||||||
// Numbers is the list of server numbers to filter VPN servers with.
|
// Numbers is the list of server numbers to filter VPN servers with.
|
||||||
Numbers []uint16 `json:"numbers"`
|
Numbers []uint16
|
||||||
// Hostnames is the list of hostnames to filter VPN servers with.
|
// Hostnames is the list of hostnames to filter VPN servers with.
|
||||||
Hostnames []string `json:"hostnames"`
|
Hostnames []string
|
||||||
// OwnedOnly is true if VPN provider servers that are not owned
|
// OwnedOnly is true if only VPN provider owned servers
|
||||||
// should be filtered. This is used with Mullvad.
|
// should be filtered. This is used with Mullvad.
|
||||||
OwnedOnly *bool `json:"owned_only"`
|
OwnedOnly *bool
|
||||||
// FreeOnly is true if VPN servers that are not free should
|
// FreeOnly is true if only free VPN servers
|
||||||
// be filtered. This is used with ProtonVPN and VPN Unlimited.
|
// should be filtered. This is used with ProtonVPN.
|
||||||
FreeOnly *bool `json:"free_only"`
|
FreeOnly *bool
|
||||||
// PremiumOnly is true if VPN servers that are not premium should
|
// FreeOnly is true if only free VPN servers
|
||||||
// be filtered. This is used with VPN Secure.
|
// should be filtered. This is used with ProtonVPN.
|
||||||
// TODO extend to providers using FreeOnly.
|
StreamOnly *bool
|
||||||
PremiumOnly *bool `json:"premium_only"`
|
// MultiHopOnly is true if only multihop VPN servers
|
||||||
// StreamOnly is true if VPN servers not for streaming should
|
|
||||||
// be filtered. This is used with VPNUnlimited.
|
|
||||||
StreamOnly *bool `json:"stream_only"`
|
|
||||||
// MultiHopOnly is true if VPN servers that are not multihop
|
|
||||||
// should be filtered. This is used with Surfshark.
|
// should be filtered. This is used with Surfshark.
|
||||||
MultiHopOnly *bool `json:"multi_hop_only"`
|
MultiHopOnly *bool
|
||||||
// PortForwardOnly is true if VPN servers that don't support
|
|
||||||
// port forwarding should be filtered. This is used with PIA.
|
|
||||||
PortForwardOnly *bool `json:"port_forward_only"`
|
|
||||||
// OpenVPN contains settings to select OpenVPN servers
|
// OpenVPN contains settings to select OpenVPN servers
|
||||||
// and the final connection.
|
// and the final connection.
|
||||||
OpenVPN OpenVPNSelection `json:"openvpn"`
|
OpenVPN OpenVPNSelection
|
||||||
// Wireguard contains settings to select Wireguard servers
|
// Wireguard contains settings to select Wireguard servers
|
||||||
// and the final connection.
|
// and the final connection.
|
||||||
Wireguard WireguardSelection `json:"wireguard"`
|
Wireguard WireguardSelection
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
|
||||||
ErrOwnedOnlyNotSupported = errors.New("owned only filter is not supported")
|
|
||||||
ErrFreeOnlyNotSupported = errors.New("free only filter is not supported")
|
|
||||||
ErrPremiumOnlyNotSupported = errors.New("premium only filter is not supported")
|
|
||||||
ErrStreamOnlyNotSupported = errors.New("stream only filter is not supported")
|
|
||||||
ErrMultiHopOnlyNotSupported = errors.New("multi hop only filter is not supported")
|
|
||||||
ErrPortForwardOnlyNotSupported = errors.New("port forwarding only filter is not supported")
|
|
||||||
ErrFreePremiumBothSet = errors.New("free only and premium only filters are both set")
|
|
||||||
)
|
|
||||||
|
|
||||||
func (ss *ServerSelection) validate(vpnServiceProvider string,
|
func (ss *ServerSelection) validate(vpnServiceProvider string,
|
||||||
storage Storage) (err error) {
|
allServers models.AllServers) (err error) {
|
||||||
switch ss.VPN {
|
switch ss.VPN {
|
||||||
case vpn.OpenVPN, vpn.Wireguard:
|
case constants.OpenVPN, constants.Wireguard:
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("%w: %s", ErrVPNTypeNotValid, ss.VPN)
|
return fmt.Errorf("%w: %s", ErrVPNTypeNotValid, ss.VPN)
|
||||||
}
|
}
|
||||||
|
|
||||||
filterChoices, err := getLocationFilterChoices(vpnServiceProvider, ss, storage)
|
var countryChoices, regionChoices, cityChoices,
|
||||||
|
ispChoices, nameChoices, hostnameChoices []string
|
||||||
|
switch vpnServiceProvider {
|
||||||
|
case constants.Custom:
|
||||||
|
case constants.Cyberghost:
|
||||||
|
servers := allServers.GetCyberghost()
|
||||||
|
countryChoices = constants.CyberghostCountryChoices(servers)
|
||||||
|
hostnameChoices = constants.CyberghostHostnameChoices(servers)
|
||||||
|
case constants.Expressvpn:
|
||||||
|
servers := allServers.GetExpressvpn()
|
||||||
|
countryChoices = constants.ExpressvpnCountriesChoices(servers)
|
||||||
|
cityChoices = constants.ExpressvpnCityChoices(servers)
|
||||||
|
hostnameChoices = constants.ExpressvpnHostnameChoices(servers)
|
||||||
|
case constants.Fastestvpn:
|
||||||
|
servers := allServers.GetFastestvpn()
|
||||||
|
countryChoices = constants.FastestvpnCountriesChoices(servers)
|
||||||
|
hostnameChoices = constants.FastestvpnHostnameChoices(servers)
|
||||||
|
case constants.HideMyAss:
|
||||||
|
servers := allServers.GetHideMyAss()
|
||||||
|
countryChoices = constants.HideMyAssCountryChoices(servers)
|
||||||
|
regionChoices = constants.HideMyAssRegionChoices(servers)
|
||||||
|
cityChoices = constants.HideMyAssCityChoices(servers)
|
||||||
|
hostnameChoices = constants.HideMyAssHostnameChoices(servers)
|
||||||
|
case constants.Ipvanish:
|
||||||
|
servers := allServers.GetIpvanish()
|
||||||
|
countryChoices = constants.IpvanishCountryChoices(servers)
|
||||||
|
cityChoices = constants.IpvanishCityChoices(servers)
|
||||||
|
hostnameChoices = constants.IpvanishHostnameChoices(servers)
|
||||||
|
case constants.Ivpn:
|
||||||
|
servers := allServers.GetIvpn()
|
||||||
|
countryChoices = constants.IvpnCountryChoices(servers)
|
||||||
|
cityChoices = constants.IvpnCityChoices(servers)
|
||||||
|
ispChoices = constants.IvpnISPChoices(servers)
|
||||||
|
hostnameChoices = constants.IvpnHostnameChoices(servers)
|
||||||
|
case constants.Mullvad:
|
||||||
|
servers := allServers.GetMullvad()
|
||||||
|
countryChoices = constants.MullvadCountryChoices(servers)
|
||||||
|
cityChoices = constants.MullvadCityChoices(servers)
|
||||||
|
ispChoices = constants.MullvadISPChoices(servers)
|
||||||
|
hostnameChoices = constants.MullvadHostnameChoices(servers)
|
||||||
|
case constants.Nordvpn:
|
||||||
|
servers := allServers.GetNordvpn()
|
||||||
|
regionChoices = constants.NordvpnRegionChoices(servers)
|
||||||
|
hostnameChoices = constants.NordvpnHostnameChoices(servers)
|
||||||
|
case constants.Perfectprivacy:
|
||||||
|
servers := allServers.GetPerfectprivacy()
|
||||||
|
cityChoices = constants.PerfectprivacyCityChoices(servers)
|
||||||
|
case constants.Privado:
|
||||||
|
servers := allServers.GetPrivado()
|
||||||
|
countryChoices = constants.PrivadoCountryChoices(servers)
|
||||||
|
regionChoices = constants.PrivadoRegionChoices(servers)
|
||||||
|
cityChoices = constants.PrivadoCityChoices(servers)
|
||||||
|
hostnameChoices = constants.PrivadoHostnameChoices(servers)
|
||||||
|
case constants.PrivateInternetAccess:
|
||||||
|
servers := allServers.GetPia()
|
||||||
|
regionChoices = constants.PIAGeoChoices(servers)
|
||||||
|
hostnameChoices = constants.PIAHostnameChoices(servers)
|
||||||
|
nameChoices = constants.PIANameChoices(servers)
|
||||||
|
case constants.Privatevpn:
|
||||||
|
servers := allServers.GetPrivatevpn()
|
||||||
|
countryChoices = constants.PrivatevpnCountryChoices(servers)
|
||||||
|
cityChoices = constants.PrivatevpnCityChoices(servers)
|
||||||
|
hostnameChoices = constants.PrivatevpnHostnameChoices(servers)
|
||||||
|
case constants.Protonvpn:
|
||||||
|
servers := allServers.GetProtonvpn()
|
||||||
|
countryChoices = constants.ProtonvpnCountryChoices(servers)
|
||||||
|
regionChoices = constants.ProtonvpnRegionChoices(servers)
|
||||||
|
cityChoices = constants.ProtonvpnCityChoices(servers)
|
||||||
|
nameChoices = constants.ProtonvpnNameChoices(servers)
|
||||||
|
hostnameChoices = constants.ProtonvpnHostnameChoices(servers)
|
||||||
|
case constants.Purevpn:
|
||||||
|
servers := allServers.GetPurevpn()
|
||||||
|
countryChoices = constants.PurevpnCountryChoices(servers)
|
||||||
|
regionChoices = constants.PurevpnRegionChoices(servers)
|
||||||
|
cityChoices = constants.PurevpnCityChoices(servers)
|
||||||
|
hostnameChoices = constants.PurevpnHostnameChoices(servers)
|
||||||
|
case constants.Surfshark:
|
||||||
|
servers := allServers.GetSurfshark()
|
||||||
|
countryChoices = constants.SurfsharkCountryChoices(servers)
|
||||||
|
cityChoices = constants.SurfsharkCityChoices(servers)
|
||||||
|
hostnameChoices = constants.SurfsharkHostnameChoices(servers)
|
||||||
|
regionChoices = constants.SurfsharkRegionChoices(servers)
|
||||||
|
// TODO v4 remove
|
||||||
|
regionChoices = append(regionChoices, constants.SurfsharkRetroLocChoices(servers)...)
|
||||||
|
if err := helpers.AreAllOneOf(ss.Regions, regionChoices); err != nil {
|
||||||
|
return fmt.Errorf("%w: %s", ErrRegionNotValid, err)
|
||||||
|
}
|
||||||
|
// Retro compatibility
|
||||||
|
// TODO remove in v4
|
||||||
|
*ss = surfsharkRetroRegion(*ss)
|
||||||
|
case constants.Torguard:
|
||||||
|
servers := allServers.GetTorguard()
|
||||||
|
countryChoices = constants.TorguardCountryChoices(servers)
|
||||||
|
cityChoices = constants.TorguardCityChoices(servers)
|
||||||
|
hostnameChoices = constants.TorguardHostnameChoices(servers)
|
||||||
|
case constants.VPNUnlimited:
|
||||||
|
servers := allServers.GetVPNUnlimited()
|
||||||
|
countryChoices = constants.VPNUnlimitedCountryChoices(servers)
|
||||||
|
cityChoices = constants.VPNUnlimitedCityChoices(servers)
|
||||||
|
hostnameChoices = constants.VPNUnlimitedHostnameChoices(servers)
|
||||||
|
case constants.Vyprvpn:
|
||||||
|
servers := allServers.GetVyprvpn()
|
||||||
|
regionChoices = constants.VyprvpnRegionChoices(servers)
|
||||||
|
case constants.Wevpn:
|
||||||
|
servers := allServers.GetWevpn()
|
||||||
|
cityChoices = constants.WevpnCityChoices(servers)
|
||||||
|
hostnameChoices = constants.WevpnHostnameChoices(servers)
|
||||||
|
case constants.Windscribe:
|
||||||
|
servers := allServers.GetWindscribe()
|
||||||
|
regionChoices = constants.WindscribeRegionChoices(servers)
|
||||||
|
cityChoices = constants.WindscribeCityChoices(servers)
|
||||||
|
hostnameChoices = constants.WindscribeHostnameChoices(servers)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("%w: %s", ErrVPNProviderNameNotValid, vpnServiceProvider)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = validateServerFilters(*ss, countryChoices, regionChoices, cityChoices,
|
||||||
|
ispChoices, nameChoices, hostnameChoices)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err // already wrapped error
|
return err // already wrapped error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retro-compatibility
|
if ss.VPN == constants.OpenVPN {
|
||||||
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 {
|
|
||||||
return fmt.Errorf("for VPN service provider %s: %w", vpnServiceProvider, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if *ss.OwnedOnly &&
|
|
||||||
vpnServiceProvider != providers.Mullvad {
|
|
||||||
return fmt.Errorf("%w: for VPN service provider %s",
|
|
||||||
ErrOwnedOnlyNotSupported, vpnServiceProvider)
|
|
||||||
}
|
|
||||||
|
|
||||||
if *ss.FreeOnly &&
|
|
||||||
!helpers.IsOneOf(vpnServiceProvider,
|
|
||||||
providers.Protonvpn,
|
|
||||||
providers.VPNUnlimited,
|
|
||||||
) {
|
|
||||||
return fmt.Errorf("%w: for VPN service provider %s",
|
|
||||||
ErrFreeOnlyNotSupported, vpnServiceProvider)
|
|
||||||
}
|
|
||||||
|
|
||||||
if *ss.PremiumOnly &&
|
|
||||||
!helpers.IsOneOf(vpnServiceProvider,
|
|
||||||
providers.VPNSecure,
|
|
||||||
) {
|
|
||||||
return fmt.Errorf("%w: for VPN service provider %s",
|
|
||||||
ErrPremiumOnlyNotSupported, vpnServiceProvider)
|
|
||||||
}
|
|
||||||
|
|
||||||
if *ss.FreeOnly && *ss.PremiumOnly {
|
|
||||||
return fmt.Errorf("%w", ErrFreePremiumBothSet)
|
|
||||||
}
|
|
||||||
|
|
||||||
if *ss.StreamOnly &&
|
|
||||||
!helpers.IsOneOf(vpnServiceProvider,
|
|
||||||
providers.Protonvpn,
|
|
||||||
providers.VPNUnlimited,
|
|
||||||
) {
|
|
||||||
return fmt.Errorf("%w: for VPN service provider %s",
|
|
||||||
ErrStreamOnlyNotSupported, vpnServiceProvider)
|
|
||||||
}
|
|
||||||
|
|
||||||
if *ss.MultiHopOnly &&
|
|
||||||
vpnServiceProvider != providers.Surfshark {
|
|
||||||
return fmt.Errorf("%w: for VPN service provider %s",
|
|
||||||
ErrMultiHopOnlyNotSupported, vpnServiceProvider)
|
|
||||||
}
|
|
||||||
|
|
||||||
if *ss.PortForwardOnly &&
|
|
||||||
vpnServiceProvider != providers.PrivateInternetAccess {
|
|
||||||
// ProtonVPN also supports port forwarding, but on all their servers, so these
|
|
||||||
// don't have the port forwarding boolean field. As a consequence, we only allow
|
|
||||||
// the use of PortForwardOnly for Private Internet Access.
|
|
||||||
return fmt.Errorf("%w: for VPN service provider %s",
|
|
||||||
ErrPortForwardOnlyNotSupported, vpnServiceProvider)
|
|
||||||
}
|
|
||||||
|
|
||||||
if ss.VPN == vpn.OpenVPN {
|
|
||||||
err = ss.OpenVPN.validate(vpnServiceProvider)
|
err = ss.OpenVPN.validate(vpnServiceProvider)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("OpenVPN server selection settings: %w", err)
|
return fmt.Errorf("OpenVPN server selection settings validation failed: %w", err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
err = ss.Wireguard.validate(vpnServiceProvider)
|
err = ss.Wireguard.validate(vpnServiceProvider)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Wireguard server selection settings: %w", err)
|
return fmt.Errorf("Wireguard server selection settings validation failed: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getLocationFilterChoices(vpnServiceProvider string,
|
// validateServerFilters validates filters against the choices given as arguments.
|
||||||
ss *ServerSelection, storage Storage) (filterChoices models.FilterChoices,
|
// Set an argument to nil to pass the check for a particular filter.
|
||||||
err error) {
|
func validateServerFilters(settings ServerSelection,
|
||||||
filterChoices = storage.GetFilterChoices(vpnServiceProvider)
|
countryChoices, regionChoices, cityChoices, ispChoices,
|
||||||
|
nameChoices, hostnameChoices []string) (err error) {
|
||||||
if vpnServiceProvider == providers.Surfshark {
|
if countryChoices != nil {
|
||||||
// // Retro compatibility
|
if err := helpers.AreAllOneOf(settings.Countries, countryChoices); err != nil {
|
||||||
// TODO v4 remove
|
return fmt.Errorf("%w: %s", ErrCountryNotValid, err)
|
||||||
newAndRetroRegions := append(filterChoices.Regions, validation.SurfsharkRetroLocChoices()...) //nolint:gocritic
|
|
||||||
err := validate.AreAllOneOfCaseInsensitive(ss.Regions, newAndRetroRegions)
|
|
||||||
if err != nil {
|
|
||||||
// Only return error comparing with newer regions, we don't want to confuse the user
|
|
||||||
// with the retro regions in the error message.
|
|
||||||
err = validate.AreAllOneOfCaseInsensitive(ss.Regions, filterChoices.Regions)
|
|
||||||
return models.FilterChoices{}, fmt.Errorf("%w: %w", ErrRegionNotValid, err)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return filterChoices, nil
|
if regionChoices != nil {
|
||||||
}
|
if err := helpers.AreAllOneOf(settings.Regions, regionChoices); err != nil {
|
||||||
|
return fmt.Errorf("%w: %s", ErrRegionNotValid, err)
|
||||||
// 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) {
|
|
||||||
err = validate.AreAllOneOfCaseInsensitive(settings.Countries, filterChoices.Countries)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("%w: %w", ErrCountryNotValid, err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err = validate.AreAllOneOfCaseInsensitive(settings.Regions, filterChoices.Regions)
|
if cityChoices != nil {
|
||||||
if err != nil {
|
if err := helpers.AreAllOneOf(settings.Cities, cityChoices); err != nil {
|
||||||
return fmt.Errorf("%w: %w", ErrRegionNotValid, err)
|
return fmt.Errorf("%w: %s", ErrCityNotValid, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = validate.AreAllOneOfCaseInsensitive(settings.Cities, filterChoices.Cities)
|
if ispChoices != nil {
|
||||||
if err != nil {
|
if err := helpers.AreAllOneOf(settings.ISPs, ispChoices); err != nil {
|
||||||
return fmt.Errorf("%w: %w", ErrCityNotValid, err)
|
return fmt.Errorf("%w: %s", ErrISPNotValid, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = validate.AreAllOneOfCaseInsensitive(settings.ISPs, filterChoices.ISPs)
|
if hostnameChoices != nil {
|
||||||
if err != nil {
|
if err := helpers.AreAllOneOf(settings.Hostnames, hostnameChoices); err != nil {
|
||||||
return fmt.Errorf("%w: %w", ErrISPNotValid, err)
|
return fmt.Errorf("%w: %s", ErrHostnameNotValid, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = validate.AreAllOneOfCaseInsensitive(settings.Hostnames, filterChoices.Hostnames)
|
if nameChoices != nil {
|
||||||
if err != nil {
|
if err := helpers.AreAllOneOf(settings.Names, nameChoices); err != nil {
|
||||||
return fmt.Errorf("%w: %w", ErrHostnameNotValid, err)
|
return fmt.Errorf("%w: %s", ErrNameNotValid, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = validate.AreAllOneOfCaseInsensitive(settings.Names, filterChoices.Names)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("%w: %w", ErrNameNotValid, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = validate.AreAllOneOfCaseInsensitive(settings.Categories, filterChoices.Categories)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("%w: %w", ErrCategoryNotValid, err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -236,79 +248,68 @@ func validateServerFilters(settings ServerSelection, filterChoices models.Filter
|
|||||||
|
|
||||||
func (ss *ServerSelection) copy() (copied ServerSelection) {
|
func (ss *ServerSelection) copy() (copied ServerSelection) {
|
||||||
return ServerSelection{
|
return ServerSelection{
|
||||||
VPN: ss.VPN,
|
VPN: ss.VPN,
|
||||||
TargetIP: ss.TargetIP,
|
TargetIP: helpers.CopyIP(ss.TargetIP),
|
||||||
Countries: gosettings.CopySlice(ss.Countries),
|
Countries: helpers.CopyStringSlice(ss.Countries),
|
||||||
Categories: gosettings.CopySlice(ss.Categories),
|
Regions: helpers.CopyStringSlice(ss.Regions),
|
||||||
Regions: gosettings.CopySlice(ss.Regions),
|
Cities: helpers.CopyStringSlice(ss.Cities),
|
||||||
Cities: gosettings.CopySlice(ss.Cities),
|
ISPs: helpers.CopyStringSlice(ss.ISPs),
|
||||||
ISPs: gosettings.CopySlice(ss.ISPs),
|
Hostnames: helpers.CopyStringSlice(ss.Hostnames),
|
||||||
Hostnames: gosettings.CopySlice(ss.Hostnames),
|
Names: helpers.CopyStringSlice(ss.Names),
|
||||||
Names: gosettings.CopySlice(ss.Names),
|
Numbers: helpers.CopyUint16Slice(ss.Numbers),
|
||||||
Numbers: gosettings.CopySlice(ss.Numbers),
|
OwnedOnly: helpers.CopyBoolPtr(ss.OwnedOnly),
|
||||||
OwnedOnly: gosettings.CopyPointer(ss.OwnedOnly),
|
FreeOnly: helpers.CopyBoolPtr(ss.FreeOnly),
|
||||||
FreeOnly: gosettings.CopyPointer(ss.FreeOnly),
|
StreamOnly: helpers.CopyBoolPtr(ss.StreamOnly),
|
||||||
PremiumOnly: gosettings.CopyPointer(ss.PremiumOnly),
|
MultiHopOnly: helpers.CopyBoolPtr(ss.MultiHopOnly),
|
||||||
StreamOnly: gosettings.CopyPointer(ss.StreamOnly),
|
OpenVPN: ss.OpenVPN.copy(),
|
||||||
PortForwardOnly: gosettings.CopyPointer(ss.PortForwardOnly),
|
Wireguard: ss.Wireguard.copy(),
|
||||||
MultiHopOnly: gosettings.CopyPointer(ss.MultiHopOnly),
|
|
||||||
OpenVPN: ss.OpenVPN.copy(),
|
|
||||||
Wireguard: ss.Wireguard.copy(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ss *ServerSelection) mergeWith(other ServerSelection) {
|
func (ss *ServerSelection) mergeWith(other ServerSelection) {
|
||||||
ss.VPN = gosettings.MergeWithString(ss.VPN, other.VPN)
|
ss.VPN = helpers.MergeWithString(ss.VPN, other.VPN)
|
||||||
ss.TargetIP = gosettings.MergeWithValidator(ss.TargetIP, other.TargetIP)
|
ss.TargetIP = helpers.MergeWithIP(ss.TargetIP, other.TargetIP)
|
||||||
ss.Countries = gosettings.MergeWithSlice(ss.Countries, other.Countries)
|
ss.Countries = helpers.MergeStringSlices(ss.Countries, other.Countries)
|
||||||
ss.Categories = gosettings.MergeWithSlice(ss.Categories, other.Categories)
|
ss.Regions = helpers.MergeStringSlices(ss.Regions, other.Regions)
|
||||||
ss.Regions = gosettings.MergeWithSlice(ss.Regions, other.Regions)
|
ss.Cities = helpers.MergeStringSlices(ss.Cities, other.Cities)
|
||||||
ss.Cities = gosettings.MergeWithSlice(ss.Cities, other.Cities)
|
ss.ISPs = helpers.MergeStringSlices(ss.ISPs, other.ISPs)
|
||||||
ss.ISPs = gosettings.MergeWithSlice(ss.ISPs, other.ISPs)
|
ss.Hostnames = helpers.MergeStringSlices(ss.Hostnames, other.Hostnames)
|
||||||
ss.Hostnames = gosettings.MergeWithSlice(ss.Hostnames, other.Hostnames)
|
ss.Names = helpers.MergeStringSlices(ss.Names, other.Names)
|
||||||
ss.Names = gosettings.MergeWithSlice(ss.Names, other.Names)
|
ss.Numbers = helpers.MergeUint16Slices(ss.Numbers, other.Numbers)
|
||||||
ss.Numbers = gosettings.MergeWithSlice(ss.Numbers, other.Numbers)
|
ss.OwnedOnly = helpers.MergeWithBool(ss.OwnedOnly, other.OwnedOnly)
|
||||||
ss.OwnedOnly = gosettings.MergeWithPointer(ss.OwnedOnly, other.OwnedOnly)
|
ss.FreeOnly = helpers.MergeWithBool(ss.FreeOnly, other.FreeOnly)
|
||||||
ss.FreeOnly = gosettings.MergeWithPointer(ss.FreeOnly, other.FreeOnly)
|
ss.StreamOnly = helpers.MergeWithBool(ss.StreamOnly, other.StreamOnly)
|
||||||
ss.PremiumOnly = gosettings.MergeWithPointer(ss.PremiumOnly, other.PremiumOnly)
|
ss.MultiHopOnly = helpers.MergeWithBool(ss.MultiHopOnly, other.MultiHopOnly)
|
||||||
ss.StreamOnly = gosettings.MergeWithPointer(ss.StreamOnly, other.StreamOnly)
|
|
||||||
ss.MultiHopOnly = gosettings.MergeWithPointer(ss.MultiHopOnly, other.MultiHopOnly)
|
|
||||||
ss.PortForwardOnly = gosettings.MergeWithPointer(ss.PortForwardOnly, other.PortForwardOnly)
|
|
||||||
|
|
||||||
ss.OpenVPN.mergeWith(other.OpenVPN)
|
ss.OpenVPN.mergeWith(other.OpenVPN)
|
||||||
ss.Wireguard.mergeWith(other.Wireguard)
|
ss.Wireguard.mergeWith(other.Wireguard)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ss *ServerSelection) overrideWith(other ServerSelection) {
|
func (ss *ServerSelection) overrideWith(other ServerSelection) {
|
||||||
ss.VPN = gosettings.OverrideWithString(ss.VPN, other.VPN)
|
ss.VPN = helpers.OverrideWithString(ss.VPN, other.VPN)
|
||||||
ss.TargetIP = gosettings.OverrideWithValidator(ss.TargetIP, other.TargetIP)
|
ss.TargetIP = helpers.OverrideWithIP(ss.TargetIP, other.TargetIP)
|
||||||
ss.Countries = gosettings.OverrideWithSlice(ss.Countries, other.Countries)
|
ss.Countries = helpers.OverrideWithStringSlice(ss.Countries, other.Countries)
|
||||||
ss.Categories = gosettings.OverrideWithSlice(ss.Categories, other.Categories)
|
ss.Regions = helpers.OverrideWithStringSlice(ss.Regions, other.Regions)
|
||||||
ss.Regions = gosettings.OverrideWithSlice(ss.Regions, other.Regions)
|
ss.Cities = helpers.OverrideWithStringSlice(ss.Cities, other.Cities)
|
||||||
ss.Cities = gosettings.OverrideWithSlice(ss.Cities, other.Cities)
|
ss.ISPs = helpers.OverrideWithStringSlice(ss.ISPs, other.ISPs)
|
||||||
ss.ISPs = gosettings.OverrideWithSlice(ss.ISPs, other.ISPs)
|
ss.Hostnames = helpers.OverrideWithStringSlice(ss.Hostnames, other.Hostnames)
|
||||||
ss.Hostnames = gosettings.OverrideWithSlice(ss.Hostnames, other.Hostnames)
|
ss.Names = helpers.OverrideWithStringSlice(ss.Names, other.Names)
|
||||||
ss.Names = gosettings.OverrideWithSlice(ss.Names, other.Names)
|
ss.Numbers = helpers.OverrideWithUint16Slice(ss.Numbers, other.Numbers)
|
||||||
ss.Numbers = gosettings.OverrideWithSlice(ss.Numbers, other.Numbers)
|
ss.OwnedOnly = helpers.OverrideWithBool(ss.OwnedOnly, other.OwnedOnly)
|
||||||
ss.OwnedOnly = gosettings.OverrideWithPointer(ss.OwnedOnly, other.OwnedOnly)
|
ss.FreeOnly = helpers.OverrideWithBool(ss.FreeOnly, other.FreeOnly)
|
||||||
ss.FreeOnly = gosettings.OverrideWithPointer(ss.FreeOnly, other.FreeOnly)
|
ss.StreamOnly = helpers.OverrideWithBool(ss.StreamOnly, other.StreamOnly)
|
||||||
ss.PremiumOnly = gosettings.OverrideWithPointer(ss.PremiumOnly, other.PremiumOnly)
|
ss.MultiHopOnly = helpers.OverrideWithBool(ss.MultiHopOnly, other.MultiHopOnly)
|
||||||
ss.StreamOnly = gosettings.OverrideWithPointer(ss.StreamOnly, other.StreamOnly)
|
|
||||||
ss.MultiHopOnly = gosettings.OverrideWithPointer(ss.MultiHopOnly, other.MultiHopOnly)
|
|
||||||
ss.PortForwardOnly = gosettings.OverrideWithPointer(ss.PortForwardOnly, other.PortForwardOnly)
|
|
||||||
ss.OpenVPN.overrideWith(other.OpenVPN)
|
ss.OpenVPN.overrideWith(other.OpenVPN)
|
||||||
ss.Wireguard.overrideWith(other.Wireguard)
|
ss.Wireguard.overrideWith(other.Wireguard)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ss *ServerSelection) setDefaults(vpnProvider string) {
|
func (ss *ServerSelection) setDefaults(vpnProvider string) {
|
||||||
ss.VPN = gosettings.DefaultString(ss.VPN, vpn.OpenVPN)
|
ss.VPN = helpers.DefaultString(ss.VPN, constants.OpenVPN)
|
||||||
ss.TargetIP = gosettings.DefaultValidator(ss.TargetIP, netip.IPv4Unspecified())
|
ss.TargetIP = helpers.DefaultIP(ss.TargetIP, net.IP{})
|
||||||
ss.OwnedOnly = gosettings.DefaultPointer(ss.OwnedOnly, false)
|
ss.OwnedOnly = helpers.DefaultBool(ss.OwnedOnly, false)
|
||||||
ss.FreeOnly = gosettings.DefaultPointer(ss.FreeOnly, false)
|
ss.FreeOnly = helpers.DefaultBool(ss.FreeOnly, false)
|
||||||
ss.PremiumOnly = gosettings.DefaultPointer(ss.PremiumOnly, false)
|
ss.StreamOnly = helpers.DefaultBool(ss.StreamOnly, false)
|
||||||
ss.StreamOnly = gosettings.DefaultPointer(ss.StreamOnly, false)
|
ss.MultiHopOnly = helpers.DefaultBool(ss.MultiHopOnly, false)
|
||||||
ss.MultiHopOnly = gosettings.DefaultPointer(ss.MultiHopOnly, false)
|
|
||||||
ss.PortForwardOnly = gosettings.DefaultPointer(ss.PortForwardOnly, false)
|
|
||||||
ss.OpenVPN.setDefaults(vpnProvider)
|
ss.OpenVPN.setDefaults(vpnProvider)
|
||||||
ss.Wireguard.setDefaults()
|
ss.Wireguard.setDefaults()
|
||||||
}
|
}
|
||||||
@@ -320,7 +321,7 @@ func (ss ServerSelection) String() string {
|
|||||||
func (ss ServerSelection) toLinesNode() (node *gotree.Node) {
|
func (ss ServerSelection) toLinesNode() (node *gotree.Node) {
|
||||||
node = gotree.New("Server selection settings:")
|
node = gotree.New("Server selection settings:")
|
||||||
node.Appendf("VPN type: %s", ss.VPN)
|
node.Appendf("VPN type: %s", ss.VPN)
|
||||||
if !ss.TargetIP.IsUnspecified() {
|
if len(ss.TargetIP) > 0 {
|
||||||
node.Appendf("Target IP address: %s", ss.TargetIP)
|
node.Appendf("Target IP address: %s", ss.TargetIP)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -328,10 +329,6 @@ func (ss ServerSelection) toLinesNode() (node *gotree.Node) {
|
|||||||
node.Appendf("Countries: %s", strings.Join(ss.Countries, ", "))
|
node.Appendf("Countries: %s", strings.Join(ss.Countries, ", "))
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(ss.Categories) > 0 {
|
|
||||||
node.Appendf("Categories: %s", strings.Join(ss.Categories, ", "))
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(ss.Regions) > 0 {
|
if len(ss.Regions) > 0 {
|
||||||
node.Appendf("Regions: %s", strings.Join(ss.Regions, ", "))
|
node.Appendf("Regions: %s", strings.Join(ss.Regions, ", "))
|
||||||
}
|
}
|
||||||
@@ -367,10 +364,6 @@ func (ss ServerSelection) toLinesNode() (node *gotree.Node) {
|
|||||||
node.Appendf("Free only servers: yes")
|
node.Appendf("Free only servers: yes")
|
||||||
}
|
}
|
||||||
|
|
||||||
if *ss.PremiumOnly {
|
|
||||||
node.Appendf("Premium only servers: yes")
|
|
||||||
}
|
|
||||||
|
|
||||||
if *ss.StreamOnly {
|
if *ss.StreamOnly {
|
||||||
node.Appendf("Stream only servers: yes")
|
node.Appendf("Stream only servers: yes")
|
||||||
}
|
}
|
||||||
@@ -379,7 +372,7 @@ func (ss ServerSelection) toLinesNode() (node *gotree.Node) {
|
|||||||
node.Appendf("Multi-hop only servers: yes")
|
node.Appendf("Multi-hop only servers: yes")
|
||||||
}
|
}
|
||||||
|
|
||||||
if ss.VPN == vpn.OpenVPN {
|
if ss.VPN == constants.OpenVPN {
|
||||||
node.AppendNode(ss.OpenVPN.toLinesNode())
|
node.AppendNode(ss.OpenVPN.toLinesNode())
|
||||||
} else {
|
} else {
|
||||||
node.AppendNode(ss.Wireguard.toLinesNode())
|
node.AppendNode(ss.Wireguard.toLinesNode())
|
||||||
|
|||||||
@@ -3,11 +3,7 @@ package settings
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"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/gluetun/internal/models"
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
"github.com/qdm12/gluetun/internal/pprof"
|
|
||||||
"github.com/qdm12/gotree"
|
"github.com/qdm12/gotree"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -24,17 +20,12 @@ type Settings struct {
|
|||||||
Updater Updater
|
Updater Updater
|
||||||
Version Version
|
Version Version
|
||||||
VPN VPN
|
VPN VPN
|
||||||
Pprof pprof.Settings
|
|
||||||
}
|
|
||||||
|
|
||||||
type Storage interface {
|
|
||||||
GetFilterChoices(provider string) models.FilterChoices
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate validates all the settings and returns an error
|
// Validate validates all the settings and returns an error
|
||||||
// if one of them is not valid.
|
// if one of them is not valid.
|
||||||
// TODO v4 remove pointer for receiver (because of Surfshark).
|
// TODO v4 remove pointer for receiver (because of Surfshark).
|
||||||
func (s *Settings) Validate(storage Storage, ipv6Supported bool) (err error) {
|
func (s *Settings) Validate(allServers models.AllServers) (err error) {
|
||||||
nameToValidation := map[string]func() error{
|
nameToValidation := map[string]func() error{
|
||||||
"control server": s.ControlServer.validate,
|
"control server": s.ControlServer.validate,
|
||||||
"dns": s.DNS.validate,
|
"dns": s.DNS.validate,
|
||||||
@@ -47,16 +38,15 @@ func (s *Settings) Validate(storage Storage, ipv6Supported bool) (err error) {
|
|||||||
"system": s.System.validate,
|
"system": s.System.validate,
|
||||||
"updater": s.Updater.Validate,
|
"updater": s.Updater.Validate,
|
||||||
"version": s.Version.validate,
|
"version": s.Version.validate,
|
||||||
// Pprof validation done in pprof constructor
|
|
||||||
"VPN": func() error {
|
"VPN": func() error {
|
||||||
return s.VPN.Validate(storage, ipv6Supported)
|
return s.VPN.validate(allServers)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for name, validation := range nameToValidation {
|
for name, validation := range nameToValidation {
|
||||||
err = validation()
|
err = validation()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s settings: %w", name, err)
|
return fmt.Errorf("failed validating %s settings: %w", name, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,8 +66,7 @@ func (s *Settings) copy() (copied Settings) {
|
|||||||
System: s.System.copy(),
|
System: s.System.copy(),
|
||||||
Updater: s.Updater.copy(),
|
Updater: s.Updater.copy(),
|
||||||
Version: s.Version.copy(),
|
Version: s.Version.copy(),
|
||||||
VPN: s.VPN.Copy(),
|
VPN: s.VPN.copy(),
|
||||||
Pprof: s.Pprof.Copy(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,11 +83,10 @@ func (s *Settings) MergeWith(other Settings) {
|
|||||||
s.Updater.mergeWith(other.Updater)
|
s.Updater.mergeWith(other.Updater)
|
||||||
s.Version.mergeWith(other.Version)
|
s.Version.mergeWith(other.Version)
|
||||||
s.VPN.mergeWith(other.VPN)
|
s.VPN.mergeWith(other.VPN)
|
||||||
s.Pprof.MergeWith(other.Pprof)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Settings) OverrideWith(other Settings,
|
func (s *Settings) OverrideWith(other Settings,
|
||||||
storage Storage, ipv6Supported bool) (err error) {
|
allServers models.AllServers) (err error) {
|
||||||
patchedSettings := s.copy()
|
patchedSettings := s.copy()
|
||||||
patchedSettings.ControlServer.overrideWith(other.ControlServer)
|
patchedSettings.ControlServer.overrideWith(other.ControlServer)
|
||||||
patchedSettings.DNS.overrideWith(other.DNS)
|
patchedSettings.DNS.overrideWith(other.DNS)
|
||||||
@@ -111,9 +99,8 @@ func (s *Settings) OverrideWith(other Settings,
|
|||||||
patchedSettings.System.overrideWith(other.System)
|
patchedSettings.System.overrideWith(other.System)
|
||||||
patchedSettings.Updater.overrideWith(other.Updater)
|
patchedSettings.Updater.overrideWith(other.Updater)
|
||||||
patchedSettings.Version.overrideWith(other.Version)
|
patchedSettings.Version.overrideWith(other.Version)
|
||||||
patchedSettings.VPN.OverrideWith(other.VPN)
|
patchedSettings.VPN.overrideWith(other.VPN)
|
||||||
patchedSettings.Pprof.OverrideWith(other.Pprof)
|
err = patchedSettings.Validate(allServers)
|
||||||
err = patchedSettings.Validate(storage, ipv6Supported)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -131,10 +118,9 @@ func (s *Settings) SetDefaults() {
|
|||||||
s.PublicIP.setDefaults()
|
s.PublicIP.setDefaults()
|
||||||
s.Shadowsocks.setDefaults()
|
s.Shadowsocks.setDefaults()
|
||||||
s.System.setDefaults()
|
s.System.setDefaults()
|
||||||
|
s.Updater.SetDefaults()
|
||||||
s.Version.setDefaults()
|
s.Version.setDefaults()
|
||||||
s.VPN.setDefaults()
|
s.VPN.setDefaults()
|
||||||
s.Updater.SetDefaults(*s.VPN.Provider.Name)
|
|
||||||
s.Pprof.SetDefaults()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s Settings) String() string {
|
func (s Settings) String() string {
|
||||||
@@ -156,28 +142,6 @@ func (s Settings) toLinesNode() (node *gotree.Node) {
|
|||||||
node.AppendNode(s.PublicIP.toLinesNode())
|
node.AppendNode(s.PublicIP.toLinesNode())
|
||||||
node.AppendNode(s.Updater.toLinesNode())
|
node.AppendNode(s.Updater.toLinesNode())
|
||||||
node.AppendNode(s.Version.toLinesNode())
|
node.AppendNode(s.Version.toLinesNode())
|
||||||
node.AppendNode(s.Pprof.ToLinesNode())
|
|
||||||
|
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s Settings) Warnings() (warnings []string) {
|
|
||||||
if *s.VPN.Provider.Name == providers.HideMyAss {
|
|
||||||
warnings = append(warnings, "HideMyAss dropped support for Linux OpenVPN "+
|
|
||||||
" so this will likely not work anymore. See https://github.com/qdm12/gluetun/issues/1498.")
|
|
||||||
}
|
|
||||||
|
|
||||||
if helpers.IsOneOf(*s.VPN.Provider.Name, providers.SlickVPN) &&
|
|
||||||
s.VPN.Type == vpn.OpenVPN {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -34,12 +34,13 @@ func Test_Settings_String(t *testing.T) {
|
|||||||
| ├── User: [not set]
|
| ├── User: [not set]
|
||||||
| ├── Password: [not set]
|
| ├── Password: [not set]
|
||||||
| ├── Private Internet Access encryption preset: strong
|
| ├── Private Internet Access encryption preset: strong
|
||||||
|
| ├── Tunnel IPv6: no
|
||||||
| ├── Network interface: tun0
|
| ├── Network interface: tun0
|
||||||
| ├── Run OpenVPN as: root
|
| ├── Run OpenVPN as: root
|
||||||
| └── Verbosity level: 1
|
| └── Verbosity level: 1
|
||||||
├── DNS settings:
|
├── DNS settings:
|
||||||
| ├── Keep existing nameserver(s): no
|
|
||||||
| ├── DNS server address to use: 127.0.0.1
|
| ├── DNS server address to use: 127.0.0.1
|
||||||
|
| ├── Keep existing nameserver(s): no
|
||||||
| └── DNS over TLS settings:
|
| └── DNS over TLS settings:
|
||||||
| ├── Enabled: yes
|
| ├── Enabled: yes
|
||||||
| ├── Update period: every 24h0m0s
|
| ├── Update period: every 24h0m0s
|
||||||
@@ -65,10 +66,7 @@ func Test_Settings_String(t *testing.T) {
|
|||||||
| └── Log level: INFO
|
| └── Log level: INFO
|
||||||
├── Health settings:
|
├── Health settings:
|
||||||
| ├── Server listening address: 127.0.0.1:9999
|
| ├── Server listening address: 127.0.0.1:9999
|
||||||
| ├── Target address: cloudflare.com:443
|
| ├── Target address: github.com:443
|
||||||
| ├── Duration to wait after success: 5s
|
|
||||||
| ├── Read header timeout: 100ms
|
|
||||||
| ├── Read timeout: 500ms
|
|
||||||
| └── VPN wait durations:
|
| └── VPN wait durations:
|
||||||
| ├── Initial duration: 6s
|
| ├── Initial duration: 6s
|
||||||
| └── Additional duration: 5s
|
| └── Additional duration: 5s
|
||||||
@@ -77,15 +75,14 @@ func Test_Settings_String(t *testing.T) {
|
|||||||
├── HTTP proxy settings:
|
├── HTTP proxy settings:
|
||||||
| └── Enabled: no
|
| └── Enabled: no
|
||||||
├── Control server settings:
|
├── Control server settings:
|
||||||
| ├── Listening address: :8000
|
| ├── Listening port: 8000
|
||||||
| └── Logging: yes
|
| └── Logging: yes
|
||||||
├── OS Alpine settings:
|
├── OS Alpine settings:
|
||||||
| ├── Process UID: 1000
|
| ├── Process UID: 1000
|
||||||
| └── Process GID: 1000
|
| └── Process GID: 1000
|
||||||
├── Public IP settings:
|
├── Public IP settings:
|
||||||
| ├── Fetching: every 12h0m0s
|
| ├── Fetching: every 12h0m0s
|
||||||
| ├── IP file path: /tmp/gluetun/ip
|
| └── IP file path: /tmp/gluetun/ip
|
||||||
| └── Public IP data API: ipinfo
|
|
||||||
└── Version settings:
|
└── Version settings:
|
||||||
└── Enabled: yes`,
|
└── Enabled: yes`,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package settings
|
package settings
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/qdm12/gosettings"
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||||
"github.com/qdm12/gotree"
|
"github.com/qdm12/gotree"
|
||||||
"github.com/qdm12/ss-server/pkg/tcpudp"
|
"github.com/qdm12/ss-server/pkg/tcpudp"
|
||||||
)
|
)
|
||||||
@@ -21,7 +21,7 @@ func (s Shadowsocks) validate() (err error) {
|
|||||||
|
|
||||||
func (s *Shadowsocks) copy() (copied Shadowsocks) {
|
func (s *Shadowsocks) copy() (copied Shadowsocks) {
|
||||||
return Shadowsocks{
|
return Shadowsocks{
|
||||||
Enabled: gosettings.CopyPointer(s.Enabled),
|
Enabled: helpers.CopyBoolPtr(s.Enabled),
|
||||||
Settings: s.Settings.Copy(),
|
Settings: s.Settings.Copy(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -29,20 +29,20 @@ func (s *Shadowsocks) copy() (copied Shadowsocks) {
|
|||||||
// mergeWith merges the other settings into any
|
// mergeWith merges the other settings into any
|
||||||
// unset field of the receiver settings object.
|
// unset field of the receiver settings object.
|
||||||
func (s *Shadowsocks) mergeWith(other Shadowsocks) {
|
func (s *Shadowsocks) mergeWith(other Shadowsocks) {
|
||||||
s.Enabled = gosettings.MergeWithPointer(s.Enabled, other.Enabled)
|
s.Enabled = helpers.MergeWithBool(s.Enabled, other.Enabled)
|
||||||
s.Settings = s.Settings.MergeWith(other.Settings)
|
s.Settings.MergeWith(other.Settings)
|
||||||
}
|
}
|
||||||
|
|
||||||
// overrideWith overrides fields of the receiver
|
// overrideWith overrides fields of the receiver
|
||||||
// settings object with any field set in the other
|
// settings object with any field set in the other
|
||||||
// settings.
|
// settings.
|
||||||
func (s *Shadowsocks) overrideWith(other Shadowsocks) {
|
func (s *Shadowsocks) overrideWith(other Shadowsocks) {
|
||||||
s.Enabled = gosettings.OverrideWithPointer(s.Enabled, other.Enabled)
|
s.Enabled = helpers.OverrideWithBool(s.Enabled, other.Enabled)
|
||||||
s.Settings.OverrideWith(other.Settings)
|
s.Settings.OverrideWith(other.Settings)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Shadowsocks) setDefaults() {
|
func (s *Shadowsocks) setDefaults() {
|
||||||
s.Enabled = gosettings.DefaultPointer(s.Enabled, false)
|
s.Enabled = helpers.DefaultBool(s.Enabled, false)
|
||||||
s.Settings.SetDefaults()
|
s.Settings.SetDefaults()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,16 +53,16 @@ func (s Shadowsocks) String() string {
|
|||||||
func (s Shadowsocks) toLinesNode() (node *gotree.Node) {
|
func (s Shadowsocks) toLinesNode() (node *gotree.Node) {
|
||||||
node = gotree.New("Shadowsocks server settings:")
|
node = gotree.New("Shadowsocks server settings:")
|
||||||
|
|
||||||
node.Appendf("Enabled: %s", gosettings.BoolToYesNo(s.Enabled))
|
node.Appendf("Enabled: %s", helpers.BoolPtrToYesNo(s.Enabled))
|
||||||
if !*s.Enabled {
|
if !*s.Enabled {
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO have ToLinesNode in qdm12/ss-server
|
// 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("Cipher: %s", s.CipherName)
|
||||||
node.Appendf("Password: %s", gosettings.ObfuscateKey(*s.Password))
|
node.Appendf("Password: %s", helpers.ObfuscatePassword(*s.Password))
|
||||||
node.Appendf("Log addresses: %s", gosettings.BoolToYesNo(s.LogAddresses))
|
node.Appendf("Log addresses: %s", helpers.BoolPtrToYesNo(s.LogAddresses))
|
||||||
|
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,14 +3,15 @@ package settings
|
|||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/provider/surfshark/servers"
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
func surfsharkRetroRegion(selection ServerSelection) (
|
func surfsharkRetroRegion(selection ServerSelection) (
|
||||||
updatedSelection ServerSelection) {
|
updatedSelection ServerSelection) {
|
||||||
locationData := servers.LocationData()
|
locationData := constants.SurfsharkLocationData()
|
||||||
|
|
||||||
retroToLocation := make(map[string]servers.ServerLocation, len(locationData))
|
retroToLocation := make(map[string]models.SurfsharkLocationData, len(locationData))
|
||||||
for _, data := range locationData {
|
for _, data := range locationData {
|
||||||
if data.RetroLoc == "" {
|
if data.RetroLoc == "" {
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
package settings
|
package settings
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/qdm12/gosettings"
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||||
"github.com/qdm12/gotree"
|
"github.com/qdm12/gotree"
|
||||||
)
|
)
|
||||||
|
|
||||||
// System contains settings to configure system related elements.
|
// System contains settings to configure system related elements.
|
||||||
type System struct {
|
type System struct {
|
||||||
PUID *uint32
|
PUID *uint16
|
||||||
PGID *uint32
|
PGID *uint16
|
||||||
Timezone string
|
Timezone string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -19,28 +19,28 @@ func (s System) validate() (err error) {
|
|||||||
|
|
||||||
func (s *System) copy() (copied System) {
|
func (s *System) copy() (copied System) {
|
||||||
return System{
|
return System{
|
||||||
PUID: gosettings.CopyPointer(s.PUID),
|
PUID: helpers.CopyUint16Ptr(s.PUID),
|
||||||
PGID: gosettings.CopyPointer(s.PGID),
|
PGID: helpers.CopyUint16Ptr(s.PGID),
|
||||||
Timezone: s.Timezone,
|
Timezone: s.Timezone,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *System) mergeWith(other System) {
|
func (s *System) mergeWith(other System) {
|
||||||
s.PUID = gosettings.MergeWithPointer(s.PUID, other.PUID)
|
s.PUID = helpers.MergeWithUint16(s.PUID, other.PUID)
|
||||||
s.PGID = gosettings.MergeWithPointer(s.PGID, other.PGID)
|
s.PGID = helpers.MergeWithUint16(s.PGID, other.PGID)
|
||||||
s.Timezone = gosettings.MergeWithString(s.Timezone, other.Timezone)
|
s.Timezone = helpers.MergeWithString(s.Timezone, other.Timezone)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *System) overrideWith(other System) {
|
func (s *System) overrideWith(other System) {
|
||||||
s.PUID = gosettings.OverrideWithPointer(s.PUID, other.PUID)
|
s.PUID = helpers.OverrideWithUint16(s.PUID, other.PUID)
|
||||||
s.PGID = gosettings.OverrideWithPointer(s.PGID, other.PGID)
|
s.PGID = helpers.OverrideWithUint16(s.PGID, other.PGID)
|
||||||
s.Timezone = gosettings.OverrideWithString(s.Timezone, other.Timezone)
|
s.Timezone = helpers.OverrideWithString(s.Timezone, other.Timezone)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *System) setDefaults() {
|
func (s *System) setDefaults() {
|
||||||
const defaultID = 1000
|
const defaultID = 1000
|
||||||
s.PUID = gosettings.DefaultPointer(s.PUID, defaultID)
|
s.PUID = helpers.DefaultUint16(s.PUID, defaultID)
|
||||||
s.PGID = gosettings.DefaultPointer(s.PGID, defaultID)
|
s.PGID = helpers.DefaultUint16(s.PGID, defaultID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s System) String() string {
|
func (s System) String() string {
|
||||||
|
|||||||
@@ -3,24 +3,25 @@ package settings
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/netip"
|
"net"
|
||||||
|
|
||||||
"github.com/qdm12/dns/pkg/provider"
|
"github.com/qdm12/dns/pkg/provider"
|
||||||
"github.com/qdm12/dns/pkg/unbound"
|
"github.com/qdm12/dns/pkg/unbound"
|
||||||
"github.com/qdm12/gosettings"
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||||
"github.com/qdm12/gotree"
|
"github.com/qdm12/gotree"
|
||||||
|
"inet.af/netaddr"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Unbound is settings for the Unbound program.
|
// Unbound is settings for the Unbound program.
|
||||||
type Unbound struct {
|
type Unbound struct {
|
||||||
Providers []string `json:"providers"`
|
Providers []string
|
||||||
Caching *bool `json:"caching"`
|
Caching *bool
|
||||||
IPv6 *bool `json:"ipv6"`
|
IPv6 *bool
|
||||||
VerbosityLevel *uint8 `json:"verbosity_level"`
|
VerbosityLevel *uint8
|
||||||
VerbosityDetailsLevel *uint8 `json:"verbosity_details_level"`
|
VerbosityDetailsLevel *uint8
|
||||||
ValidationLogLevel *uint8 `json:"validation_log_level"`
|
ValidationLogLevel *uint8
|
||||||
Username string `json:"username"`
|
Username string
|
||||||
Allowed []netip.Prefix `json:"allowed"`
|
Allowed []netaddr.IPPrefix
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *Unbound) setDefaults() {
|
func (u *Unbound) setDefaults() {
|
||||||
@@ -30,26 +31,26 @@ func (u *Unbound) setDefaults() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
u.Caching = gosettings.DefaultPointer(u.Caching, true)
|
u.Caching = helpers.DefaultBool(u.Caching, true)
|
||||||
u.IPv6 = gosettings.DefaultPointer(u.IPv6, false)
|
u.IPv6 = helpers.DefaultBool(u.IPv6, false)
|
||||||
|
|
||||||
const defaultVerbosityLevel = 1
|
const defaultVerbosityLevel = 1
|
||||||
u.VerbosityLevel = gosettings.DefaultPointer(u.VerbosityLevel, defaultVerbosityLevel)
|
u.VerbosityLevel = helpers.DefaultUint8(u.VerbosityLevel, defaultVerbosityLevel)
|
||||||
|
|
||||||
const defaultVerbosityDetailsLevel = 0
|
const defaultVerbosityDetailsLevel = 0
|
||||||
u.VerbosityDetailsLevel = gosettings.DefaultPointer(u.VerbosityDetailsLevel, defaultVerbosityDetailsLevel)
|
u.VerbosityDetailsLevel = helpers.DefaultUint8(u.VerbosityDetailsLevel, defaultVerbosityDetailsLevel)
|
||||||
|
|
||||||
const defaultValidationLogLevel = 0
|
const defaultValidationLogLevel = 0
|
||||||
u.ValidationLogLevel = gosettings.DefaultPointer(u.ValidationLogLevel, defaultValidationLogLevel)
|
u.ValidationLogLevel = helpers.DefaultUint8(u.ValidationLogLevel, defaultValidationLogLevel)
|
||||||
|
|
||||||
if u.Allowed == nil {
|
if u.Allowed == nil {
|
||||||
u.Allowed = []netip.Prefix{
|
u.Allowed = []netaddr.IPPrefix{
|
||||||
netip.PrefixFrom(netip.AddrFrom4([4]byte{}), 0),
|
netaddr.IPPrefixFrom(netaddr.IPv4(0, 0, 0, 0), 0),
|
||||||
netip.PrefixFrom(netip.AddrFrom16([16]byte{}), 0),
|
netaddr.IPPrefixFrom(netaddr.IPv6Raw([16]byte{}), 0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
u.Username = gosettings.DefaultString(u.Username, "root")
|
u.Username = helpers.DefaultString(u.Username, "root")
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -94,37 +95,37 @@ func (u Unbound) validate() (err error) {
|
|||||||
|
|
||||||
func (u Unbound) copy() (copied Unbound) {
|
func (u Unbound) copy() (copied Unbound) {
|
||||||
return Unbound{
|
return Unbound{
|
||||||
Providers: gosettings.CopySlice(u.Providers),
|
Providers: helpers.CopyStringSlice(u.Providers),
|
||||||
Caching: gosettings.CopyPointer(u.Caching),
|
Caching: helpers.CopyBoolPtr(u.Caching),
|
||||||
IPv6: gosettings.CopyPointer(u.IPv6),
|
IPv6: helpers.CopyBoolPtr(u.IPv6),
|
||||||
VerbosityLevel: gosettings.CopyPointer(u.VerbosityLevel),
|
VerbosityLevel: helpers.CopyUint8Ptr(u.VerbosityLevel),
|
||||||
VerbosityDetailsLevel: gosettings.CopyPointer(u.VerbosityDetailsLevel),
|
VerbosityDetailsLevel: helpers.CopyUint8Ptr(u.VerbosityDetailsLevel),
|
||||||
ValidationLogLevel: gosettings.CopyPointer(u.ValidationLogLevel),
|
ValidationLogLevel: helpers.CopyUint8Ptr(u.ValidationLogLevel),
|
||||||
Username: u.Username,
|
Username: u.Username,
|
||||||
Allowed: gosettings.CopySlice(u.Allowed),
|
Allowed: helpers.CopyIPPrefixSlice(u.Allowed),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *Unbound) mergeWith(other Unbound) {
|
func (u *Unbound) mergeWith(other Unbound) {
|
||||||
u.Providers = gosettings.MergeWithSlice(u.Providers, other.Providers)
|
u.Providers = helpers.MergeStringSlices(u.Providers, other.Providers)
|
||||||
u.Caching = gosettings.MergeWithPointer(u.Caching, other.Caching)
|
u.Caching = helpers.MergeWithBool(u.Caching, other.Caching)
|
||||||
u.IPv6 = gosettings.MergeWithPointer(u.IPv6, other.IPv6)
|
u.IPv6 = helpers.MergeWithBool(u.IPv6, other.IPv6)
|
||||||
u.VerbosityLevel = gosettings.MergeWithPointer(u.VerbosityLevel, other.VerbosityLevel)
|
u.VerbosityLevel = helpers.MergeWithUint8(u.VerbosityLevel, other.VerbosityLevel)
|
||||||
u.VerbosityDetailsLevel = gosettings.MergeWithPointer(u.VerbosityDetailsLevel, other.VerbosityDetailsLevel)
|
u.VerbosityDetailsLevel = helpers.MergeWithUint8(u.VerbosityDetailsLevel, other.VerbosityDetailsLevel)
|
||||||
u.ValidationLogLevel = gosettings.MergeWithPointer(u.ValidationLogLevel, other.ValidationLogLevel)
|
u.ValidationLogLevel = helpers.MergeWithUint8(u.ValidationLogLevel, other.ValidationLogLevel)
|
||||||
u.Username = gosettings.MergeWithString(u.Username, other.Username)
|
u.Username = helpers.MergeWithString(u.Username, other.Username)
|
||||||
u.Allowed = gosettings.MergeWithSlice(u.Allowed, other.Allowed)
|
u.Allowed = helpers.MergeIPPrefixesSlices(u.Allowed, other.Allowed)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *Unbound) overrideWith(other Unbound) {
|
func (u *Unbound) overrideWith(other Unbound) {
|
||||||
u.Providers = gosettings.OverrideWithSlice(u.Providers, other.Providers)
|
u.Providers = helpers.OverrideWithStringSlice(u.Providers, other.Providers)
|
||||||
u.Caching = gosettings.OverrideWithPointer(u.Caching, other.Caching)
|
u.Caching = helpers.OverrideWithBool(u.Caching, other.Caching)
|
||||||
u.IPv6 = gosettings.OverrideWithPointer(u.IPv6, other.IPv6)
|
u.IPv6 = helpers.OverrideWithBool(u.IPv6, other.IPv6)
|
||||||
u.VerbosityLevel = gosettings.OverrideWithPointer(u.VerbosityLevel, other.VerbosityLevel)
|
u.VerbosityLevel = helpers.OverrideWithUint8(u.VerbosityLevel, other.VerbosityLevel)
|
||||||
u.VerbosityDetailsLevel = gosettings.OverrideWithPointer(u.VerbosityDetailsLevel, other.VerbosityDetailsLevel)
|
u.VerbosityDetailsLevel = helpers.OverrideWithUint8(u.VerbosityDetailsLevel, other.VerbosityDetailsLevel)
|
||||||
u.ValidationLogLevel = gosettings.OverrideWithPointer(u.ValidationLogLevel, other.ValidationLogLevel)
|
u.ValidationLogLevel = helpers.OverrideWithUint8(u.ValidationLogLevel, other.ValidationLogLevel)
|
||||||
u.Username = gosettings.OverrideWithString(u.Username, other.Username)
|
u.Username = helpers.OverrideWithString(u.Username, other.Username)
|
||||||
u.Allowed = gosettings.OverrideWithSlice(u.Allowed, other.Allowed)
|
u.Allowed = helpers.OverrideWithIPPrefixesSlice(u.Allowed, other.Allowed)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u Unbound) ToUnboundFormat() (settings unbound.Settings, err error) {
|
func (u Unbound) ToUnboundFormat() (settings unbound.Settings, err error) {
|
||||||
@@ -148,30 +149,20 @@ func (u Unbound) ToUnboundFormat() (settings unbound.Settings, err error) {
|
|||||||
VerbosityDetailsLevel: *u.VerbosityDetailsLevel,
|
VerbosityDetailsLevel: *u.VerbosityDetailsLevel,
|
||||||
ValidationLogLevel: *u.ValidationLogLevel,
|
ValidationLogLevel: *u.ValidationLogLevel,
|
||||||
AccessControl: unbound.AccessControlSettings{
|
AccessControl: unbound.AccessControlSettings{
|
||||||
Allowed: netipPrefixesToNetaddrIPPrefixes(u.Allowed),
|
Allowed: u.Allowed,
|
||||||
},
|
},
|
||||||
Username: u.Username,
|
Username: u.Username,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
func (u Unbound) GetFirstPlaintextIPv4() (ipv4 net.IP, err error) {
|
||||||
ErrConvertingNetip = errors.New("converting net.IP to netip.Addr failed")
|
|
||||||
)
|
|
||||||
|
|
||||||
func (u Unbound) GetFirstPlaintextIPv4() (ipv4 netip.Addr, err error) {
|
|
||||||
s := u.Providers[0]
|
s := u.Providers[0]
|
||||||
provider, err := provider.Parse(s)
|
provider, err := provider.Parse(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ipv4, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
ip := provider.DNS().IPv4[0]
|
return provider.DNS().IPv4[0], nil
|
||||||
ipv4, ok := netip.AddrFromSlice(ip)
|
|
||||||
if !ok {
|
|
||||||
return ipv4, fmt.Errorf("%w: for ip %s (%#v)",
|
|
||||||
ErrConvertingNetip, ip, ip)
|
|
||||||
}
|
|
||||||
return ipv4.Unmap(), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u Unbound) String() string {
|
func (u Unbound) String() string {
|
||||||
@@ -186,8 +177,8 @@ func (u Unbound) toLinesNode() (node *gotree.Node) {
|
|||||||
authServers.Appendf(provider)
|
authServers.Appendf(provider)
|
||||||
}
|
}
|
||||||
|
|
||||||
node.Appendf("Caching: %s", gosettings.BoolToYesNo(u.Caching))
|
node.Appendf("Caching: %s", helpers.BoolPtrToYesNo(u.Caching))
|
||||||
node.Appendf("IPv6: %s", gosettings.BoolToYesNo(u.IPv6))
|
node.Appendf("IPv6: %s", helpers.BoolPtrToYesNo(u.IPv6))
|
||||||
node.Appendf("Verbosity level: %d", *u.VerbosityLevel)
|
node.Appendf("Verbosity level: %d", *u.VerbosityLevel)
|
||||||
node.Appendf("Verbosity details level: %d", *u.VerbosityDetailsLevel)
|
node.Appendf("Verbosity details level: %d", *u.VerbosityDetailsLevel)
|
||||||
node.Appendf("Validation log level: %d", *u.ValidationLogLevel)
|
node.Appendf("Validation log level: %d", *u.ValidationLogLevel)
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ package settings
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/netip"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
"inet.af/netaddr"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_Unbound_JSON(t *testing.T) {
|
func Test_Unbound_JSON(t *testing.T) {
|
||||||
@@ -20,18 +20,18 @@ func Test_Unbound_JSON(t *testing.T) {
|
|||||||
VerbosityDetailsLevel: nil,
|
VerbosityDetailsLevel: nil,
|
||||||
ValidationLogLevel: uint8Ptr(0),
|
ValidationLogLevel: uint8Ptr(0),
|
||||||
Username: "user",
|
Username: "user",
|
||||||
Allowed: []netip.Prefix{
|
Allowed: []netaddr.IPPrefix{
|
||||||
netip.PrefixFrom(netip.AddrFrom4([4]byte{}), 0),
|
netaddr.IPPrefixFrom(netaddr.IPv4(0, 0, 0, 0), 0),
|
||||||
netip.PrefixFrom(netip.AddrFrom16([16]byte{}), 0),
|
netaddr.IPPrefixFrom(netaddr.IPv6Raw([16]byte{}), 0),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
b, err := json.Marshal(settings)
|
b, err := json.Marshal(settings)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
const expected = `{"providers":["cloudflare"],"caching":true,"ipv6":false,` +
|
const expected = `{"Providers":["cloudflare"],"Caching":true,"IPv6":false,` +
|
||||||
`"verbosity_level":1,"verbosity_details_level":null,"validation_log_level":0,` +
|
`"VerbosityLevel":1,"VerbosityDetailsLevel":null,"ValidationLogLevel":0,` +
|
||||||
`"username":"user","allowed":["0.0.0.0/0","::/0"]}`
|
`"Username":"user","Allowed":["0.0.0.0/0","::/0"]}`
|
||||||
|
|
||||||
assert.Equal(t, expected, string(b))
|
assert.Equal(t, expected, string(b))
|
||||||
|
|
||||||
|
|||||||
@@ -2,12 +2,12 @@ package settings
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||||
"github.com/qdm12/gosettings"
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
"github.com/qdm12/gosettings/validate"
|
|
||||||
"github.com/qdm12/gotree"
|
"github.com/qdm12/gotree"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -21,15 +21,16 @@ type Updater struct {
|
|||||||
Period *time.Duration
|
Period *time.Duration
|
||||||
// DNSAddress is the DNS server address to use
|
// DNSAddress is the DNS server address to use
|
||||||
// to resolve VPN server hostnames to IP addresses.
|
// to resolve VPN server hostnames to IP addresses.
|
||||||
// It cannot be the empty string in the internal state.
|
// It cannot be nil in the internal state.
|
||||||
DNSAddress string
|
DNSAddress net.IP
|
||||||
// MinRatio is the minimum ratio of servers to
|
|
||||||
// find per provider, compared to the total current
|
|
||||||
// number of servers. It defaults to 0.8.
|
|
||||||
MinRatio float64
|
|
||||||
// Providers is the list of VPN service providers
|
// Providers is the list of VPN service providers
|
||||||
// to update server information for.
|
// to update server information for.
|
||||||
Providers []string
|
Providers []string
|
||||||
|
// CLI is to precise the updater is running in CLI
|
||||||
|
// mode. This is set automatically and cannot be set
|
||||||
|
// by settings sources. It cannot be nil in the
|
||||||
|
// internal state.
|
||||||
|
CLI *bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u Updater) Validate() (err error) {
|
func (u Updater) Validate() (err error) {
|
||||||
@@ -39,16 +40,21 @@ func (u Updater) Validate() (err error) {
|
|||||||
ErrUpdaterPeriodTooSmall, *u.Period, minPeriod)
|
ErrUpdaterPeriodTooSmall, *u.Period, minPeriod)
|
||||||
}
|
}
|
||||||
|
|
||||||
if u.MinRatio <= 0 || u.MinRatio > 1 {
|
for i, provider := range u.Providers {
|
||||||
return fmt.Errorf("%w: %.2f must be between 0+ and 1",
|
valid := false
|
||||||
ErrMinRatioNotValid, u.MinRatio)
|
for _, validProvider := range constants.AllProviders() {
|
||||||
}
|
if validProvider == constants.Custom {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
validProviders := providers.All()
|
if provider == validProvider {
|
||||||
for _, provider := range u.Providers {
|
valid = true
|
||||||
err = validate.IsOneOf(provider, validProviders...)
|
break
|
||||||
if err != nil {
|
}
|
||||||
return fmt.Errorf("%w: %w", ErrVPNProviderNameNotValid, err)
|
}
|
||||||
|
if !valid {
|
||||||
|
return fmt.Errorf("%w: %s at index %d",
|
||||||
|
ErrVPNProviderNameNotValid, provider, i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,44 +63,36 @@ func (u Updater) Validate() (err error) {
|
|||||||
|
|
||||||
func (u *Updater) copy() (copied Updater) {
|
func (u *Updater) copy() (copied Updater) {
|
||||||
return Updater{
|
return Updater{
|
||||||
Period: gosettings.CopyPointer(u.Period),
|
Period: helpers.CopyDurationPtr(u.Period),
|
||||||
DNSAddress: u.DNSAddress,
|
DNSAddress: helpers.CopyIP(u.DNSAddress),
|
||||||
MinRatio: u.MinRatio,
|
Providers: helpers.CopyStringSlice(u.Providers),
|
||||||
Providers: gosettings.CopySlice(u.Providers),
|
CLI: u.CLI,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// mergeWith merges the other settings into any
|
// mergeWith merges the other settings into any
|
||||||
// unset field of the receiver settings object.
|
// unset field of the receiver settings object.
|
||||||
func (u *Updater) mergeWith(other Updater) {
|
func (u *Updater) mergeWith(other Updater) {
|
||||||
u.Period = gosettings.MergeWithPointer(u.Period, other.Period)
|
u.Period = helpers.MergeWithDuration(u.Period, other.Period)
|
||||||
u.DNSAddress = gosettings.MergeWithString(u.DNSAddress, other.DNSAddress)
|
u.DNSAddress = helpers.MergeWithIP(u.DNSAddress, other.DNSAddress)
|
||||||
u.MinRatio = gosettings.MergeWithNumber(u.MinRatio, other.MinRatio)
|
u.Providers = helpers.MergeStringSlices(u.Providers, other.Providers)
|
||||||
u.Providers = gosettings.MergeWithSlice(u.Providers, other.Providers)
|
u.CLI = helpers.MergeWithBool(u.CLI, other.CLI)
|
||||||
}
|
}
|
||||||
|
|
||||||
// overrideWith overrides fields of the receiver
|
// overrideWith overrides fields of the receiver
|
||||||
// settings object with any field set in the other
|
// settings object with any field set in the other
|
||||||
// settings.
|
// settings.
|
||||||
func (u *Updater) overrideWith(other Updater) {
|
func (u *Updater) overrideWith(other Updater) {
|
||||||
u.Period = gosettings.OverrideWithPointer(u.Period, other.Period)
|
u.Period = helpers.OverrideWithDuration(u.Period, other.Period)
|
||||||
u.DNSAddress = gosettings.OverrideWithString(u.DNSAddress, other.DNSAddress)
|
u.DNSAddress = helpers.OverrideWithIP(u.DNSAddress, other.DNSAddress)
|
||||||
u.MinRatio = gosettings.OverrideWithNumber(u.MinRatio, other.MinRatio)
|
u.Providers = helpers.OverrideWithStringSlice(u.Providers, other.Providers)
|
||||||
u.Providers = gosettings.OverrideWithSlice(u.Providers, other.Providers)
|
u.CLI = helpers.MergeWithBool(u.CLI, other.CLI)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *Updater) SetDefaults(vpnProvider string) {
|
func (u *Updater) SetDefaults() {
|
||||||
u.Period = gosettings.DefaultPointer(u.Period, 0)
|
u.Period = helpers.DefaultDuration(u.Period, 0)
|
||||||
u.DNSAddress = gosettings.DefaultString(u.DNSAddress, "1.1.1.1:53")
|
u.DNSAddress = helpers.DefaultIP(u.DNSAddress, net.IPv4(1, 1, 1, 1))
|
||||||
|
u.CLI = helpers.DefaultBool(u.CLI, false)
|
||||||
if u.MinRatio == 0 {
|
|
||||||
const defaultMinRatio = 0.8
|
|
||||||
u.MinRatio = defaultMinRatio
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(u.Providers) == 0 && vpnProvider != providers.Custom {
|
|
||||||
u.Providers = []string{vpnProvider}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u Updater) String() string {
|
func (u Updater) String() string {
|
||||||
@@ -102,15 +100,18 @@ func (u Updater) String() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u Updater) toLinesNode() (node *gotree.Node) {
|
func (u Updater) toLinesNode() (node *gotree.Node) {
|
||||||
if *u.Period == 0 || len(u.Providers) == 0 {
|
if *u.Period == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
node = gotree.New("Server data updater settings:")
|
node = gotree.New("Server data updater settings:")
|
||||||
node.Appendf("Update period: %s", *u.Period)
|
node.Appendf("Update period: %s", *u.Period)
|
||||||
node.Appendf("DNS address: %s", u.DNSAddress)
|
node.Appendf("DNS address: %s", u.DNSAddress)
|
||||||
node.Appendf("Minimum ratio: %.1f", u.MinRatio)
|
|
||||||
node.Appendf("Providers to update: %s", strings.Join(u.Providers, ", "))
|
node.Appendf("Providers to update: %s", strings.Join(u.Providers, ", "))
|
||||||
|
|
||||||
|
if *u.CLI {
|
||||||
|
node.Appendf("CLI mode: enabled")
|
||||||
|
}
|
||||||
|
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,151 +0,0 @@
|
|||||||
package validation
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sort"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
|
||||||
)
|
|
||||||
|
|
||||||
func sortedInsert(ss []string, s string) []string {
|
|
||||||
i := sort.SearchStrings(ss, s)
|
|
||||||
ss = append(ss, "")
|
|
||||||
copy(ss[i+1:], ss[i:])
|
|
||||||
ss[i] = s
|
|
||||||
return ss
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExtractCountries(servers []models.Server) (values []string) {
|
|
||||||
seen := make(map[string]struct{}, len(servers))
|
|
||||||
values = make([]string, 0, len(servers))
|
|
||||||
for _, server := range servers {
|
|
||||||
value := server.Country
|
|
||||||
if value == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
_, alreadySeen := seen[value]
|
|
||||||
if alreadySeen {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
seen[value] = struct{}{}
|
|
||||||
|
|
||||||
values = sortedInsert(values, value)
|
|
||||||
}
|
|
||||||
return values
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExtractCategories(servers []models.Server) (values []string) {
|
|
||||||
seen := make(map[string]struct{}, len(servers))
|
|
||||||
values = make([]string, 0, len(servers))
|
|
||||||
for _, server := range servers {
|
|
||||||
categories := server.Categories
|
|
||||||
if len(categories) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, value := range categories {
|
|
||||||
_, alreadySeen := seen[value]
|
|
||||||
if alreadySeen {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
seen[value] = struct{}{}
|
|
||||||
|
|
||||||
values = sortedInsert(values, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return values
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExtractRegions(servers []models.Server) (values []string) {
|
|
||||||
seen := make(map[string]struct{}, len(servers))
|
|
||||||
values = make([]string, 0, len(servers))
|
|
||||||
for _, server := range servers {
|
|
||||||
value := server.Region
|
|
||||||
if value == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
_, alreadySeen := seen[value]
|
|
||||||
if alreadySeen {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
seen[value] = struct{}{}
|
|
||||||
|
|
||||||
values = sortedInsert(values, value)
|
|
||||||
}
|
|
||||||
return values
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExtractCities(servers []models.Server) (values []string) {
|
|
||||||
seen := make(map[string]struct{}, len(servers))
|
|
||||||
values = make([]string, 0, len(servers))
|
|
||||||
for _, server := range servers {
|
|
||||||
value := server.City
|
|
||||||
if value == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
_, alreadySeen := seen[value]
|
|
||||||
if alreadySeen {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
seen[value] = struct{}{}
|
|
||||||
|
|
||||||
values = sortedInsert(values, value)
|
|
||||||
}
|
|
||||||
return values
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExtractISPs(servers []models.Server) (values []string) {
|
|
||||||
seen := make(map[string]struct{}, len(servers))
|
|
||||||
values = make([]string, 0, len(servers))
|
|
||||||
for _, server := range servers {
|
|
||||||
value := server.ISP
|
|
||||||
if value == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
_, alreadySeen := seen[value]
|
|
||||||
if alreadySeen {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
seen[value] = struct{}{}
|
|
||||||
|
|
||||||
values = sortedInsert(values, value)
|
|
||||||
}
|
|
||||||
return values
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExtractServerNames(servers []models.Server) (values []string) {
|
|
||||||
seen := make(map[string]struct{}, len(servers))
|
|
||||||
values = make([]string, 0, len(servers))
|
|
||||||
for _, server := range servers {
|
|
||||||
value := server.ServerName
|
|
||||||
if value == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
_, alreadySeen := seen[value]
|
|
||||||
if alreadySeen {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
seen[value] = struct{}{}
|
|
||||||
|
|
||||||
values = sortedInsert(values, value)
|
|
||||||
}
|
|
||||||
return values
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExtractHostnames(servers []models.Server) (values []string) {
|
|
||||||
seen := make(map[string]struct{}, len(servers))
|
|
||||||
values = make([]string, 0, len(servers))
|
|
||||||
for _, server := range servers {
|
|
||||||
value := server.Hostname
|
|
||||||
if value == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
_, alreadySeen := seen[value]
|
|
||||||
if alreadySeen {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
seen[value] = struct{}{}
|
|
||||||
|
|
||||||
values = sortedInsert(values, value)
|
|
||||||
}
|
|
||||||
return values
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
package validation
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/qdm12/gluetun/internal/provider/surfshark/servers"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TODO remove in v4.
|
|
||||||
func SurfsharkRetroLocChoices() (choices []string) {
|
|
||||||
locationData := servers.LocationData()
|
|
||||||
choices = make([]string, 0, len(locationData))
|
|
||||||
seen := make(map[string]struct{}, len(locationData))
|
|
||||||
for _, data := range locationData {
|
|
||||||
if data.RetroLoc == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if _, ok := seen[data.RetroLoc]; ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
seen[data.RetroLoc] = struct{}{}
|
|
||||||
choices = sortedInsert(choices, data.RetroLoc)
|
|
||||||
}
|
|
||||||
|
|
||||||
return choices
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
package settings
|
package settings
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/qdm12/gosettings"
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||||
"github.com/qdm12/gotree"
|
"github.com/qdm12/gotree"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -19,25 +19,25 @@ func (v Version) validate() (err error) {
|
|||||||
|
|
||||||
func (v *Version) copy() (copied Version) {
|
func (v *Version) copy() (copied Version) {
|
||||||
return Version{
|
return Version{
|
||||||
Enabled: gosettings.CopyPointer(v.Enabled),
|
Enabled: helpers.CopyBoolPtr(v.Enabled),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// mergeWith merges the other settings into any
|
// mergeWith merges the other settings into any
|
||||||
// unset field of the receiver settings object.
|
// unset field of the receiver settings object.
|
||||||
func (v *Version) mergeWith(other Version) {
|
func (v *Version) mergeWith(other Version) {
|
||||||
v.Enabled = gosettings.MergeWithPointer(v.Enabled, other.Enabled)
|
v.Enabled = helpers.MergeWithBool(v.Enabled, other.Enabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
// overrideWith overrides fields of the receiver
|
// overrideWith overrides fields of the receiver
|
||||||
// settings object with any field set in the other
|
// settings object with any field set in the other
|
||||||
// settings.
|
// settings.
|
||||||
func (v *Version) overrideWith(other Version) {
|
func (v *Version) overrideWith(other Version) {
|
||||||
v.Enabled = gosettings.OverrideWithPointer(v.Enabled, other.Enabled)
|
v.Enabled = helpers.OverrideWithBool(v.Enabled, other.Enabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Version) setDefaults() {
|
func (v *Version) setDefaults() {
|
||||||
v.Enabled = gosettings.DefaultPointer(v.Enabled, true)
|
v.Enabled = helpers.DefaultBool(v.Enabled, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v Version) String() string {
|
func (v Version) String() string {
|
||||||
@@ -47,7 +47,7 @@ func (v Version) String() string {
|
|||||||
func (v Version) toLinesNode() (node *gotree.Node) {
|
func (v Version) toLinesNode() (node *gotree.Node) {
|
||||||
node = gotree.New("Version settings:")
|
node = gotree.New("Version settings:")
|
||||||
|
|
||||||
node.Appendf("Enabled: %s", gosettings.BoolToYesNo(v.Enabled))
|
node.Appendf("Enabled: %s", helpers.BoolPtrToYesNo(v.Enabled))
|
||||||
|
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,10 +2,11 @@ package settings
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/constants/vpn"
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||||
"github.com/qdm12/gosettings"
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
"github.com/qdm12/gosettings/validate"
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
"github.com/qdm12/gotree"
|
"github.com/qdm12/gotree"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -13,41 +14,42 @@ type VPN struct {
|
|||||||
// Type is the VPN type and can only be
|
// Type is the VPN type and can only be
|
||||||
// 'openvpn' or 'wireguard'. It cannot be the
|
// 'openvpn' or 'wireguard'. It cannot be the
|
||||||
// empty string in the internal state.
|
// empty string in the internal state.
|
||||||
Type string `json:"type"`
|
Type string
|
||||||
Provider Provider `json:"provider"`
|
Provider Provider
|
||||||
OpenVPN OpenVPN `json:"openvpn"`
|
OpenVPN OpenVPN
|
||||||
Wireguard Wireguard `json:"wireguard"`
|
Wireguard Wireguard
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO v4 remove pointer for receiver (because of Surfshark).
|
// TODO v4 remove pointer for receiver (because of Surfshark).
|
||||||
func (v *VPN) Validate(storage Storage, ipv6Supported bool) (err error) {
|
func (v *VPN) validate(allServers models.AllServers) (err error) {
|
||||||
// Validate Type
|
// Validate Type
|
||||||
validVPNTypes := []string{vpn.OpenVPN, vpn.Wireguard}
|
validVPNTypes := []string{constants.OpenVPN, constants.Wireguard}
|
||||||
if err = validate.IsOneOf(v.Type, validVPNTypes...); err != nil {
|
if !helpers.IsOneOf(v.Type, validVPNTypes...) {
|
||||||
return fmt.Errorf("%w: %w", ErrVPNTypeNotValid, err)
|
return fmt.Errorf("%w: %q and can only be one of %s",
|
||||||
|
ErrVPNTypeNotValid, v.Type, strings.Join(validVPNTypes, ", "))
|
||||||
}
|
}
|
||||||
|
|
||||||
err = v.Provider.validate(v.Type, storage)
|
err = v.Provider.validate(v.Type, allServers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("provider settings: %w", err)
|
return fmt.Errorf("provider settings validation failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if v.Type == vpn.OpenVPN {
|
if v.Type == constants.OpenVPN {
|
||||||
err := v.OpenVPN.validate(*v.Provider.Name)
|
err := v.OpenVPN.validate(*v.Provider.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("OpenVPN settings: %w", err)
|
return fmt.Errorf("OpenVPN settings validation failed: %w", err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
err := v.Wireguard.validate(*v.Provider.Name, ipv6Supported)
|
err := v.Wireguard.validate(*v.Provider.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Wireguard settings: %w", err)
|
return fmt.Errorf("Wireguard settings validation failed: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *VPN) Copy() (copied VPN) {
|
func (v *VPN) copy() (copied VPN) {
|
||||||
return VPN{
|
return VPN{
|
||||||
Type: v.Type,
|
Type: v.Type,
|
||||||
Provider: v.Provider.copy(),
|
Provider: v.Provider.copy(),
|
||||||
@@ -57,24 +59,24 @@ func (v *VPN) Copy() (copied VPN) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (v *VPN) mergeWith(other VPN) {
|
func (v *VPN) mergeWith(other VPN) {
|
||||||
v.Type = gosettings.MergeWithString(v.Type, other.Type)
|
v.Type = helpers.MergeWithString(v.Type, other.Type)
|
||||||
v.Provider.mergeWith(other.Provider)
|
v.Provider.mergeWith(other.Provider)
|
||||||
v.OpenVPN.mergeWith(other.OpenVPN)
|
v.OpenVPN.mergeWith(other.OpenVPN)
|
||||||
v.Wireguard.mergeWith(other.Wireguard)
|
v.Wireguard.mergeWith(other.Wireguard)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *VPN) OverrideWith(other VPN) {
|
func (v *VPN) overrideWith(other VPN) {
|
||||||
v.Type = gosettings.OverrideWithString(v.Type, other.Type)
|
v.Type = helpers.OverrideWithString(v.Type, other.Type)
|
||||||
v.Provider.overrideWith(other.Provider)
|
v.Provider.overrideWith(other.Provider)
|
||||||
v.OpenVPN.overrideWith(other.OpenVPN)
|
v.OpenVPN.overrideWith(other.OpenVPN)
|
||||||
v.Wireguard.overrideWith(other.Wireguard)
|
v.Wireguard.overrideWith(other.Wireguard)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *VPN) setDefaults() {
|
func (v *VPN) setDefaults() {
|
||||||
v.Type = gosettings.DefaultString(v.Type, vpn.OpenVPN)
|
v.Type = helpers.DefaultString(v.Type, constants.OpenVPN)
|
||||||
v.Provider.setDefaults()
|
v.Provider.setDefaults()
|
||||||
v.OpenVPN.setDefaults(*v.Provider.Name)
|
v.OpenVPN.setDefaults(*v.Provider.Name)
|
||||||
v.Wireguard.setDefaults(*v.Provider.Name)
|
v.Wireguard.setDefaults()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v VPN) String() string {
|
func (v VPN) String() string {
|
||||||
@@ -86,7 +88,7 @@ func (v VPN) toLinesNode() (node *gotree.Node) {
|
|||||||
|
|
||||||
node.AppendNode(v.Provider.toLinesNode())
|
node.AppendNode(v.Provider.toLinesNode())
|
||||||
|
|
||||||
if v.Type == vpn.OpenVPN {
|
if v.Type == constants.OpenVPN {
|
||||||
node.AppendNode(v.OpenVPN.toLinesNode())
|
node.AppendNode(v.OpenVPN.toLinesNode())
|
||||||
} else {
|
} else {
|
||||||
node.AppendNode(v.Wireguard.toLinesNode())
|
node.AppendNode(v.Wireguard.toLinesNode())
|
||||||
|
|||||||
@@ -2,13 +2,11 @@ package settings
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/netip"
|
"net"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
"github.com/qdm12/gosettings"
|
|
||||||
"github.com/qdm12/gosettings/validate"
|
|
||||||
"github.com/qdm12/gotree"
|
"github.com/qdm12/gotree"
|
||||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
)
|
)
|
||||||
@@ -17,48 +15,30 @@ import (
|
|||||||
type Wireguard struct {
|
type Wireguard struct {
|
||||||
// PrivateKey is the Wireguard client peer private key.
|
// PrivateKey is the Wireguard client peer private key.
|
||||||
// It cannot be nil in the internal state.
|
// It cannot be nil in the internal state.
|
||||||
PrivateKey *string `json:"private_key"`
|
PrivateKey *string
|
||||||
// PreSharedKey is the Wireguard pre-shared key.
|
// PreSharedKey is the Wireguard pre-shared key.
|
||||||
// It can be the empty string to indicate there
|
// It can be the empty string to indicate there
|
||||||
// is no pre-shared key.
|
// is no pre-shared key.
|
||||||
// It cannot be nil in the internal state.
|
// It cannot be nil in the internal state.
|
||||||
PreSharedKey *string `json:"pre_shared_key"`
|
PreSharedKey *string
|
||||||
// Addresses are the Wireguard interface addresses.
|
// Addresses are the Wireguard interface addresses.
|
||||||
Addresses []netip.Prefix `json:"addresses"`
|
Addresses []net.IPNet
|
||||||
// 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
|
// Interface is the name of the Wireguard interface
|
||||||
// to create. It cannot be the empty string in the
|
// to create. It cannot be the empty string in the
|
||||||
// internal state.
|
// internal state.
|
||||||
Interface string `json:"interface"`
|
Interface string
|
||||||
// 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 `json:"implementation"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var regexpInterfaceName = regexp.MustCompile(`^[a-zA-Z0-9_]+$`)
|
var regexpInterfaceName = regexp.MustCompile(`^[a-zA-Z0-9_]+$`)
|
||||||
|
|
||||||
// Validate validates Wireguard settings.
|
// Validate validates Wireguard settings.
|
||||||
// It should only be ran if the VPN type chosen is Wireguard.
|
// It should only be ran if the VPN type chosen is Wireguard.
|
||||||
func (w Wireguard) validate(vpnProvider string, ipv6Supported bool) (err error) {
|
func (w Wireguard) validate(vpnProvider string) (err error) {
|
||||||
if !helpers.IsOneOf(vpnProvider,
|
if !helpers.IsOneOf(vpnProvider,
|
||||||
providers.Airvpn,
|
constants.Custom,
|
||||||
providers.Custom,
|
constants.Ivpn,
|
||||||
providers.Ivpn,
|
constants.Mullvad,
|
||||||
providers.Mullvad,
|
constants.Windscribe,
|
||||||
providers.Nordvpn,
|
|
||||||
providers.Surfshark,
|
|
||||||
providers.Windscribe,
|
|
||||||
) {
|
) {
|
||||||
// do not validate for VPN provider not supporting Wireguard
|
// do not validate for VPN provider not supporting Wireguard
|
||||||
return nil
|
return nil
|
||||||
@@ -66,58 +46,29 @@ func (w Wireguard) validate(vpnProvider string, ipv6Supported bool) (err error)
|
|||||||
|
|
||||||
// Validate PrivateKey
|
// Validate PrivateKey
|
||||||
if *w.PrivateKey == "" {
|
if *w.PrivateKey == "" {
|
||||||
return fmt.Errorf("%w", ErrWireguardPrivateKeyNotSet)
|
return ErrWireguardPrivateKeyNotSet
|
||||||
}
|
}
|
||||||
_, err = wgtypes.ParseKey(*w.PrivateKey)
|
_, err = wgtypes.ParseKey(*w.PrivateKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("private key is not valid: %w", err)
|
return fmt.Errorf("%w: %s", ErrWireguardPrivateKeyNotValid, err)
|
||||||
if vpnProvider == providers.Nordvpn &&
|
|
||||||
err.Error() == "wgtypes: incorrect key size: 48" {
|
|
||||||
err = fmt.Errorf("%w - you might be using your access token instead of the Wireguard private key", err)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if vpnProvider == providers.Airvpn {
|
|
||||||
if *w.PreSharedKey == "" {
|
|
||||||
return fmt.Errorf("%w", ErrWireguardPreSharedKeyNotSet)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate PreSharedKey
|
// Validate PreSharedKey
|
||||||
if *w.PreSharedKey != "" { // Note: this is optional
|
if *w.PreSharedKey != "" { // Note: this is optional
|
||||||
_, err = wgtypes.ParseKey(*w.PreSharedKey)
|
_, err = wgtypes.ParseKey(*w.PreSharedKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("pre-shared key is not valid: %w", err)
|
return fmt.Errorf("%w: %s", ErrWireguardPreSharedKeyNotValid, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate Addresses
|
// Validate Addresses
|
||||||
if len(w.Addresses) == 0 {
|
if len(w.Addresses) == 0 {
|
||||||
return fmt.Errorf("%w", ErrWireguardInterfaceAddressNotSet)
|
return ErrWireguardInterfaceAddressNotSet
|
||||||
}
|
}
|
||||||
for i, ipNet := range w.Addresses {
|
for i, ipNet := range w.Addresses {
|
||||||
if !ipNet.IsValid() {
|
if ipNet.IP == nil || ipNet.Mask == nil {
|
||||||
return fmt.Errorf("%w: for address at index %d",
|
return fmt.Errorf("%w: for address at index %d: %s",
|
||||||
ErrWireguardInterfaceAddressNotSet, i)
|
ErrWireguardInterfaceAddressNotSet, i, ipNet.String())
|
||||||
}
|
|
||||||
|
|
||||||
if !ipv6Supported && ipNet.Addr().Is6() {
|
|
||||||
return fmt.Errorf("%w: address %s",
|
|
||||||
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))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,63 +78,36 @@ func (w Wireguard) validate(vpnProvider string, ipv6Supported bool) (err error)
|
|||||||
ErrWireguardInterfaceNotValid, w.Interface, regexpInterfaceName)
|
ErrWireguardInterfaceNotValid, w.Interface, regexpInterfaceName)
|
||||||
}
|
}
|
||||||
|
|
||||||
validImplementations := []string{"auto", "userspace", "kernelspace"}
|
|
||||||
if err := validate.IsOneOf(w.Implementation, validImplementations...); err != nil {
|
|
||||||
return fmt.Errorf("%w: %w", ErrWireguardImplementationNotValid, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Wireguard) copy() (copied Wireguard) {
|
func (w *Wireguard) copy() (copied Wireguard) {
|
||||||
return Wireguard{
|
return Wireguard{
|
||||||
PrivateKey: gosettings.CopyPointer(w.PrivateKey),
|
PrivateKey: helpers.CopyStringPtr(w.PrivateKey),
|
||||||
PreSharedKey: gosettings.CopyPointer(w.PreSharedKey),
|
PreSharedKey: helpers.CopyStringPtr(w.PreSharedKey),
|
||||||
Addresses: gosettings.CopySlice(w.Addresses),
|
Addresses: helpers.CopyIPNetSlice(w.Addresses),
|
||||||
AllowedIPs: gosettings.CopySlice(w.AllowedIPs),
|
Interface: w.Interface,
|
||||||
Interface: w.Interface,
|
|
||||||
MTU: w.MTU,
|
|
||||||
Implementation: w.Implementation,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Wireguard) mergeWith(other Wireguard) {
|
func (w *Wireguard) mergeWith(other Wireguard) {
|
||||||
w.PrivateKey = gosettings.MergeWithPointer(w.PrivateKey, other.PrivateKey)
|
w.PrivateKey = helpers.MergeWithStringPtr(w.PrivateKey, other.PrivateKey)
|
||||||
w.PreSharedKey = gosettings.MergeWithPointer(w.PreSharedKey, other.PreSharedKey)
|
w.PreSharedKey = helpers.MergeWithStringPtr(w.PreSharedKey, other.PreSharedKey)
|
||||||
w.Addresses = gosettings.MergeWithSlice(w.Addresses, other.Addresses)
|
w.Addresses = helpers.MergeIPNetsSlices(w.Addresses, other.Addresses)
|
||||||
w.AllowedIPs = gosettings.MergeWithSlice(w.AllowedIPs, other.AllowedIPs)
|
w.Interface = helpers.MergeWithString(w.Interface, other.Interface)
|
||||||
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) {
|
func (w *Wireguard) overrideWith(other Wireguard) {
|
||||||
w.PrivateKey = gosettings.OverrideWithPointer(w.PrivateKey, other.PrivateKey)
|
w.PrivateKey = helpers.OverrideWithStringPtr(w.PrivateKey, other.PrivateKey)
|
||||||
w.PreSharedKey = gosettings.OverrideWithPointer(w.PreSharedKey, other.PreSharedKey)
|
w.PreSharedKey = helpers.OverrideWithStringPtr(w.PreSharedKey, other.PreSharedKey)
|
||||||
w.Addresses = gosettings.OverrideWithSlice(w.Addresses, other.Addresses)
|
w.Addresses = helpers.OverrideWithIPNetsSlice(w.Addresses, other.Addresses)
|
||||||
w.AllowedIPs = gosettings.OverrideWithSlice(w.AllowedIPs, other.AllowedIPs)
|
w.Interface = helpers.OverrideWithString(w.Interface, other.Interface)
|
||||||
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(vpnProvider string) {
|
func (w *Wireguard) setDefaults() {
|
||||||
w.PrivateKey = gosettings.DefaultPointer(w.PrivateKey, "")
|
w.PrivateKey = helpers.DefaultStringPtr(w.PrivateKey, "")
|
||||||
w.PreSharedKey = gosettings.DefaultPointer(w.PreSharedKey, "")
|
w.PreSharedKey = helpers.DefaultStringPtr(w.PreSharedKey, "")
|
||||||
if vpnProvider == providers.Nordvpn {
|
w.Interface = helpers.DefaultString(w.Interface, "wg0")
|
||||||
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 {
|
func (w Wireguard) String() string {
|
||||||
@@ -194,12 +118,12 @@ func (w Wireguard) toLinesNode() (node *gotree.Node) {
|
|||||||
node = gotree.New("Wireguard settings:")
|
node = gotree.New("Wireguard settings:")
|
||||||
|
|
||||||
if *w.PrivateKey != "" {
|
if *w.PrivateKey != "" {
|
||||||
s := gosettings.ObfuscateKey(*w.PrivateKey)
|
s := helpers.ObfuscateWireguardKey(*w.PrivateKey)
|
||||||
node.Appendf("Private key: %s", s)
|
node.Appendf("Private key: %s", s)
|
||||||
}
|
}
|
||||||
|
|
||||||
if *w.PreSharedKey != "" {
|
if *w.PreSharedKey != "" {
|
||||||
s := gosettings.ObfuscateKey(*w.PreSharedKey)
|
s := helpers.ObfuscateWireguardKey(*w.PreSharedKey)
|
||||||
node.Appendf("Pre-shared key: %s", s)
|
node.Appendf("Pre-shared key: %s", s)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,17 +132,7 @@ func (w Wireguard) toLinesNode() (node *gotree.Node) {
|
|||||||
addressesNode.Appendf(address.String())
|
addressesNode.Appendf(address.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
allowedIPsNode := node.Appendf("Allowed IPs:")
|
node.Appendf("Network interface: %s", w.Interface)
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,10 @@ package settings
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/netip"
|
"net"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||||
"github.com/qdm12/gosettings"
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
"github.com/qdm12/gosettings/validate"
|
|
||||||
"github.com/qdm12/gotree"
|
"github.com/qdm12/gotree"
|
||||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
)
|
)
|
||||||
@@ -16,20 +15,20 @@ type WireguardSelection struct {
|
|||||||
// It is only used with VPN providers generating Wireguard
|
// It is only used with VPN providers generating Wireguard
|
||||||
// configurations specific to each server and user.
|
// configurations specific to each server and user.
|
||||||
// To indicate it should not be used, it should be set
|
// To indicate it should not be used, it should be set
|
||||||
// to netip.IPv4Unspecified(). It can never be the zero value
|
// to the empty net.IP{} slice. It can never be nil
|
||||||
// in the internal state.
|
// in the internal state.
|
||||||
EndpointIP netip.Addr `json:"endpoint_ip"`
|
EndpointIP net.IP
|
||||||
// EndpointPort is a the server port to use for the VPN server.
|
// EndpointPort is a the server port to use for the VPN server.
|
||||||
// It is optional for VPN providers IVPN, Mullvad, Surfshark
|
// It is optional for VPN providers IVPN, Mullvad
|
||||||
// and Windscribe, and compulsory for the others.
|
// and Windscribe, and compulsory for the others.
|
||||||
// When optional, it can be set to 0 to indicate not use
|
// When optional, it can be set to 0 to indicate not use
|
||||||
// a custom endpoint port. It cannot be nil in the internal
|
// a custom endpoint port. It cannot be nil in the internal
|
||||||
// state.
|
// state.
|
||||||
EndpointPort *uint16 `json:"endpoint_port"`
|
EndpointPort *uint16
|
||||||
// PublicKey is the server public key.
|
// PublicKey is the server public key.
|
||||||
// It is only used with VPN providers generating Wireguard
|
// It is only used with VPN providers generating Wireguard
|
||||||
// configurations specific to each server and user.
|
// configurations specific to each server and user.
|
||||||
PublicKey string `json:"public_key"`
|
PublicKey string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate validates WireguardSelection settings.
|
// Validate validates WireguardSelection settings.
|
||||||
@@ -37,12 +36,10 @@ type WireguardSelection struct {
|
|||||||
func (w WireguardSelection) validate(vpnProvider string) (err error) {
|
func (w WireguardSelection) validate(vpnProvider string) (err error) {
|
||||||
// Validate EndpointIP
|
// Validate EndpointIP
|
||||||
switch vpnProvider {
|
switch vpnProvider {
|
||||||
case providers.Airvpn, providers.Ivpn, providers.Mullvad,
|
case constants.Ivpn, constants.Mullvad, constants.Windscribe: // endpoint IP addresses are baked in
|
||||||
providers.Nordvpn, providers.Surfshark, providers.Windscribe:
|
case constants.Custom:
|
||||||
// endpoint IP addresses are baked in
|
if len(w.EndpointIP) == 0 {
|
||||||
case providers.Custom:
|
return ErrWireguardEndpointIPNotSet
|
||||||
if !w.EndpointIP.IsValid() || w.EndpointIP.IsUnspecified() {
|
|
||||||
return fmt.Errorf("%w", ErrWireguardEndpointIPNotSet)
|
|
||||||
}
|
}
|
||||||
default: // Providers not supporting Wireguard
|
default: // Providers not supporting Wireguard
|
||||||
}
|
}
|
||||||
@@ -50,50 +47,41 @@ func (w WireguardSelection) validate(vpnProvider string) (err error) {
|
|||||||
// Validate EndpointPort
|
// Validate EndpointPort
|
||||||
switch vpnProvider {
|
switch vpnProvider {
|
||||||
// EndpointPort is required
|
// EndpointPort is required
|
||||||
case providers.Custom:
|
case constants.Custom:
|
||||||
if *w.EndpointPort == 0 {
|
if *w.EndpointPort == 0 {
|
||||||
return fmt.Errorf("%w", ErrWireguardEndpointPortNotSet)
|
return ErrWireguardEndpointPortNotSet
|
||||||
}
|
}
|
||||||
// EndpointPort cannot be set
|
case constants.Ivpn, constants.Mullvad, constants.Windscribe:
|
||||||
case providers.Surfshark, providers.Nordvpn:
|
|
||||||
if *w.EndpointPort != 0 {
|
|
||||||
return fmt.Errorf("%w", ErrWireguardEndpointPortSet)
|
|
||||||
}
|
|
||||||
case providers.Airvpn, providers.Ivpn, providers.Mullvad, providers.Windscribe:
|
|
||||||
// EndpointPort is optional and can be 0
|
// EndpointPort is optional and can be 0
|
||||||
if *w.EndpointPort == 0 {
|
if *w.EndpointPort == 0 {
|
||||||
break // no custom endpoint port set
|
break // no custom endpoint port set
|
||||||
}
|
}
|
||||||
if vpnProvider == providers.Mullvad {
|
if vpnProvider == constants.Mullvad {
|
||||||
break // no restriction on custom endpoint port value
|
break // no restriction on custom endpoint port value
|
||||||
}
|
}
|
||||||
var allowed []uint16
|
var allowed []uint16
|
||||||
switch vpnProvider {
|
switch vpnProvider {
|
||||||
case providers.Airvpn:
|
case constants.Ivpn:
|
||||||
allowed = []uint16{1637, 47107}
|
|
||||||
case providers.Ivpn:
|
|
||||||
allowed = []uint16{2049, 2050, 53, 30587, 41893, 48574, 58237}
|
allowed = []uint16{2049, 2050, 53, 30587, 41893, 48574, 58237}
|
||||||
case providers.Windscribe:
|
case constants.Windscribe:
|
||||||
allowed = []uint16{53, 80, 123, 443, 1194, 65142}
|
allowed = []uint16{53, 80, 123, 443, 1194, 65142}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = validate.IsOneOf(*w.EndpointPort, allowed...)
|
if helpers.Uint16IsOneOf(*w.EndpointPort, allowed) {
|
||||||
if err == nil {
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
return fmt.Errorf("%w: for VPN service provider %s: %w",
|
return fmt.Errorf("%w: %d for VPN service provider %s; %s",
|
||||||
ErrWireguardEndpointPortNotAllowed, vpnProvider, err)
|
ErrWireguardEndpointPortNotAllowed, w.EndpointPort, vpnProvider,
|
||||||
|
helpers.PortChoicesOrString(allowed))
|
||||||
default: // Providers not supporting Wireguard
|
default: // Providers not supporting Wireguard
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate PublicKey
|
// Validate PublicKey
|
||||||
switch vpnProvider {
|
switch vpnProvider {
|
||||||
case providers.Ivpn, providers.Mullvad,
|
case constants.Ivpn, constants.Mullvad, constants.Windscribe: // public keys are baked in
|
||||||
providers.Surfshark, providers.Windscribe:
|
case constants.Custom:
|
||||||
// public keys are baked in
|
|
||||||
case providers.Custom:
|
|
||||||
if w.PublicKey == "" {
|
if w.PublicKey == "" {
|
||||||
return fmt.Errorf("%w", ErrWireguardPublicKeyNotSet)
|
return ErrWireguardPublicKeyNotSet
|
||||||
}
|
}
|
||||||
default: // Providers not supporting Wireguard
|
default: // Providers not supporting Wireguard
|
||||||
}
|
}
|
||||||
@@ -110,27 +98,27 @@ func (w WireguardSelection) validate(vpnProvider string) (err error) {
|
|||||||
|
|
||||||
func (w *WireguardSelection) copy() (copied WireguardSelection) {
|
func (w *WireguardSelection) copy() (copied WireguardSelection) {
|
||||||
return WireguardSelection{
|
return WireguardSelection{
|
||||||
EndpointIP: w.EndpointIP,
|
EndpointIP: helpers.CopyIP(w.EndpointIP),
|
||||||
EndpointPort: gosettings.CopyPointer(w.EndpointPort),
|
EndpointPort: helpers.CopyUint16Ptr(w.EndpointPort),
|
||||||
PublicKey: w.PublicKey,
|
PublicKey: w.PublicKey,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *WireguardSelection) mergeWith(other WireguardSelection) {
|
func (w *WireguardSelection) mergeWith(other WireguardSelection) {
|
||||||
w.EndpointIP = gosettings.MergeWithValidator(w.EndpointIP, other.EndpointIP)
|
w.EndpointIP = helpers.MergeWithIP(w.EndpointIP, other.EndpointIP)
|
||||||
w.EndpointPort = gosettings.MergeWithPointer(w.EndpointPort, other.EndpointPort)
|
w.EndpointPort = helpers.MergeWithUint16(w.EndpointPort, other.EndpointPort)
|
||||||
w.PublicKey = gosettings.MergeWithString(w.PublicKey, other.PublicKey)
|
w.PublicKey = helpers.MergeWithString(w.PublicKey, other.PublicKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *WireguardSelection) overrideWith(other WireguardSelection) {
|
func (w *WireguardSelection) overrideWith(other WireguardSelection) {
|
||||||
w.EndpointIP = gosettings.OverrideWithValidator(w.EndpointIP, other.EndpointIP)
|
w.EndpointIP = helpers.OverrideWithIP(w.EndpointIP, other.EndpointIP)
|
||||||
w.EndpointPort = gosettings.OverrideWithPointer(w.EndpointPort, other.EndpointPort)
|
w.EndpointPort = helpers.OverrideWithUint16(w.EndpointPort, other.EndpointPort)
|
||||||
w.PublicKey = gosettings.OverrideWithString(w.PublicKey, other.PublicKey)
|
w.PublicKey = helpers.OverrideWithString(w.PublicKey, other.PublicKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *WireguardSelection) setDefaults() {
|
func (w *WireguardSelection) setDefaults() {
|
||||||
w.EndpointIP = gosettings.DefaultValidator(w.EndpointIP, netip.IPv4Unspecified())
|
w.EndpointIP = helpers.DefaultIP(w.EndpointIP, net.IP{})
|
||||||
w.EndpointPort = gosettings.DefaultPointer(w.EndpointPort, 0)
|
w.EndpointPort = helpers.DefaultUint16(w.EndpointPort, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w WireguardSelection) String() string {
|
func (w WireguardSelection) String() string {
|
||||||
@@ -140,7 +128,7 @@ func (w WireguardSelection) String() string {
|
|||||||
func (w WireguardSelection) toLinesNode() (node *gotree.Node) {
|
func (w WireguardSelection) toLinesNode() (node *gotree.Node) {
|
||||||
node = gotree.New("Wireguard selection settings:")
|
node = gotree.New("Wireguard selection settings:")
|
||||||
|
|
||||||
if !w.EndpointIP.IsUnspecified() {
|
if len(w.EndpointIP) > 0 {
|
||||||
node.Appendf("Endpoint IP address: %s", w.EndpointIP)
|
node.Appendf("Endpoint IP address: %s", w.EndpointIP)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
40
internal/configuration/sources/env/dns.go
vendored
40
internal/configuration/sources/env/dns.go
vendored
@@ -2,51 +2,47 @@ package env
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/netip"
|
"net"
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *Source) readDNS() (dns settings.DNS, err error) {
|
func (r *Reader) readDNS() (dns settings.DNS, err error) {
|
||||||
dns.ServerAddress, err = s.readDNSServerAddress()
|
dns.ServerAddress, err = r.readDNSServerAddress()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return dns, err
|
return dns, err
|
||||||
}
|
}
|
||||||
|
|
||||||
dns.KeepNameserver, err = s.env.BoolPtr("DNS_KEEP_NAMESERVER")
|
dns.KeepNameserver, err = envToBoolPtr("DNS_KEEP_NAMESERVER")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return dns, err
|
return dns, fmt.Errorf("environment variable DNS_KEEP_NAMESERVER: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
dns.DoT, err = s.readDoT()
|
dns.DoT, err = r.readDoT()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return dns, fmt.Errorf("DoT settings: %w", err)
|
return dns, fmt.Errorf("cannot read DoT settings: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return dns, nil
|
return dns, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Source) readDNSServerAddress() (address netip.Addr, err error) {
|
func (r *Reader) readDNSServerAddress() (address net.IP, err error) {
|
||||||
const currentKey = "DNS_ADDRESS"
|
s := os.Getenv("DNS_PLAINTEXT_ADDRESS")
|
||||||
key := firstKeySet(s.env, "DNS_PLAINTEXT_ADDRESS", currentKey)
|
if s == "" {
|
||||||
switch key {
|
return nil, nil
|
||||||
case "":
|
|
||||||
return address, nil
|
|
||||||
case currentKey:
|
|
||||||
default: // Retro-compatibility
|
|
||||||
s.handleDeprecatedKey(key, currentKey)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
address, err = s.env.NetipAddr(key)
|
address = net.ParseIP(s)
|
||||||
if err != nil {
|
if address == nil {
|
||||||
return address, err
|
return nil, fmt.Errorf("environment variable DNS_PLAINTEXT_ADDRESS: %w: %s", ErrIPAddressParse, s)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO remove in v4
|
// TODO remove in v4
|
||||||
if address.Unmap().Compare(netip.AddrFrom4([4]byte{127, 0, 0, 1})) != 0 {
|
if !address.Equal(net.IPv4(127, 0, 0, 1)) { //nolint:gomnd
|
||||||
s.warner.Warn(key + " is set to " + address.String() +
|
r.warner.Warn("DNS_PLAINTEXT_ADDRESS is set to " + s +
|
||||||
" so the DNS over TLS (DoT) server will not be used." +
|
" so the DNS over TLS (DoT) server will not be used." +
|
||||||
" The default value changed to 127.0.0.1 so it uses the internal DoT serves." +
|
" The default value changed to 127.0.0.1 so it uses the internal DoT server." +
|
||||||
" If the DoT server fails to start, the IPv4 address of the first plaintext DNS server" +
|
" If the DoT server fails to start, the IPv4 address of the first plaintext DNS server" +
|
||||||
" corresponding to the first DoT provider chosen is used.")
|
" corresponding to the first DoT provider chosen is used.")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,62 +3,79 @@ package env
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/netip"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||||
"github.com/qdm12/gosettings/sources/env"
|
"inet.af/netaddr"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *Source) readDNSBlacklist() (blacklist settings.DNSBlacklist, err error) {
|
func (r *Reader) readDNSBlacklist() (blacklist settings.DNSBlacklist, err error) {
|
||||||
blacklist.BlockMalicious, err = s.env.BoolPtr("BLOCK_MALICIOUS")
|
blacklist.BlockMalicious, err = envToBoolPtr("BLOCK_MALICIOUS")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return blacklist, err
|
return blacklist, fmt.Errorf("environment variable BLOCK_MALICIOUS: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
blacklist.BlockSurveillance, err = s.env.BoolPtr("BLOCK_SURVEILLANCE",
|
blacklist.BlockSurveillance, err = r.readBlockSurveillance()
|
||||||
env.RetroKeys("BLOCK_NSA"))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return blacklist, err
|
return blacklist, fmt.Errorf("environment variable BLOCK_MALICIOUS: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
blacklist.BlockAds, err = s.env.BoolPtr("BLOCK_ADS")
|
blacklist.BlockAds, err = envToBoolPtr("BLOCK_ADS")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return blacklist, err
|
return blacklist, fmt.Errorf("environment variable BLOCK_ADS: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
blacklist.AddBlockedIPs, blacklist.AddBlockedIPPrefixes,
|
blacklist.AddBlockedIPs, blacklist.AddBlockedIPPrefixes,
|
||||||
err = s.readDoTPrivateAddresses() // TODO v4 split in 2
|
err = readDoTPrivateAddresses() // TODO v4 split in 2
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return blacklist, err
|
return blacklist, err
|
||||||
}
|
}
|
||||||
|
|
||||||
blacklist.AllowedHosts = s.env.CSV("UNBLOCK") // TODO v4 change name
|
blacklist.AllowedHosts = envToCSV("UNBLOCK") // TODO v4 change name
|
||||||
|
|
||||||
return blacklist, nil
|
return blacklist, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Reader) readBlockSurveillance() (blocked *bool, err error) {
|
||||||
|
blocked, err = envToBoolPtr("BLOCK_SURVEILLANCE")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("environment variable BLOCK_SURVEILLANCE: %w", err)
|
||||||
|
} else if blocked != nil {
|
||||||
|
return blocked, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
blocked, err = envToBoolPtr("BLOCK_NSA")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("environment variable BLOCK_NSA: %w", err)
|
||||||
|
} else if blocked != nil {
|
||||||
|
r.onRetroActive("BLOCK_NSA", "BLOCK_SURVEILLANCE")
|
||||||
|
return blocked, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil //nolint:nilnil
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrPrivateAddressNotValid = errors.New("private address is not a valid IP or CIDR range")
|
ErrPrivateAddressNotValid = errors.New("private address is not a valid IP or CIDR range")
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *Source) readDoTPrivateAddresses() (ips []netip.Addr,
|
func readDoTPrivateAddresses() (ips []netaddr.IP,
|
||||||
ipPrefixes []netip.Prefix, err error) {
|
ipPrefixes []netaddr.IPPrefix, err error) {
|
||||||
privateAddresses := s.env.CSV("DOT_PRIVATE_ADDRESS")
|
privateAddresses := envToCSV("DOT_PRIVATE_ADDRESS")
|
||||||
if len(privateAddresses) == 0 {
|
if len(privateAddresses) == 0 {
|
||||||
return nil, nil, nil
|
return nil, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
ips = make([]netip.Addr, 0, len(privateAddresses))
|
ips = make([]netaddr.IP, 0, len(privateAddresses))
|
||||||
ipPrefixes = make([]netip.Prefix, 0, len(privateAddresses))
|
ipPrefixes = make([]netaddr.IPPrefix, 0, len(privateAddresses))
|
||||||
|
|
||||||
for _, privateAddress := range privateAddresses {
|
for _, privateAddress := range privateAddresses {
|
||||||
ip, err := netip.ParseAddr(privateAddress)
|
ip, err := netaddr.ParseIP(privateAddress)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
ips = append(ips, ip)
|
ips = append(ips, ip)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
ipPrefix, err := netip.ParsePrefix(privateAddress)
|
ipPrefix, err := netaddr.ParseIPPrefix(privateAddress)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
ipPrefixes = append(ipPrefixes, ipPrefix)
|
ipPrefixes = append(ipPrefixes, ipPrefix)
|
||||||
continue
|
continue
|
||||||
|
|||||||
28
internal/configuration/sources/env/dot.go
vendored
28
internal/configuration/sources/env/dot.go
vendored
@@ -1,26 +1,28 @@
|
|||||||
package env
|
package env
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *Source) readDoT() (dot settings.DoT, err error) {
|
func (r *Reader) readDoT() (dot settings.DoT, err error) {
|
||||||
dot.Enabled, err = s.env.BoolPtr("DOT")
|
dot.Enabled, err = envToBoolPtr("DOT")
|
||||||
|
if err != nil {
|
||||||
|
return dot, fmt.Errorf("environment variable DOT: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
dot.UpdatePeriod, err = envToDurationPtr("DNS_UPDATE_PERIOD")
|
||||||
|
if err != nil {
|
||||||
|
return dot, fmt.Errorf("environment variable DNS_UPDATE_PERIOD: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
dot.Unbound, err = readUnbound()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return dot, err
|
return dot, err
|
||||||
}
|
}
|
||||||
|
|
||||||
dot.UpdatePeriod, err = s.env.DurationPtr("DNS_UPDATE_PERIOD")
|
dot.Blacklist, err = r.readDNSBlacklist()
|
||||||
if err != nil {
|
|
||||||
return dot, err
|
|
||||||
}
|
|
||||||
|
|
||||||
dot.Unbound, err = s.readUnbound()
|
|
||||||
if err != nil {
|
|
||||||
return dot, err
|
|
||||||
}
|
|
||||||
|
|
||||||
dot.Blacklist, err = s.readDNSBlacklist()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return dot, err
|
return dot, err
|
||||||
}
|
}
|
||||||
|
|||||||
86
internal/configuration/sources/env/firewall.go
vendored
86
internal/configuration/sources/env/firewall.go
vendored
@@ -1,36 +1,96 @@
|
|||||||
package env
|
package env
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||||
"github.com/qdm12/gosettings/sources/env"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *Source) readFirewall() (firewall settings.Firewall, err error) {
|
func (r *Reader) readFirewall() (firewall settings.Firewall, err error) {
|
||||||
firewall.VPNInputPorts, err = s.env.CSVUint16("FIREWALL_VPN_INPUT_PORTS")
|
vpnInputPortStrings := envToCSV("FIREWALL_VPN_INPUT_PORTS")
|
||||||
|
firewall.VPNInputPorts, err = stringsToPorts(vpnInputPortStrings)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return firewall, err
|
return firewall, fmt.Errorf("environment variable FIREWALL_VPN_INPUT_PORTS: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
firewall.InputPorts, err = s.env.CSVUint16("FIREWALL_INPUT_PORTS")
|
inputPortStrings := envToCSV("FIREWALL_INPUT_PORTS")
|
||||||
|
firewall.InputPorts, err = stringsToPorts(inputPortStrings)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return firewall, err
|
return firewall, fmt.Errorf("environment variable FIREWALL_INPUT_PORTS: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
firewall.OutboundSubnets, err = s.env.CSVNetipPrefixes("FIREWALL_OUTBOUND_SUBNETS",
|
outboundSubnetsKey := "FIREWALL_OUTBOUND_SUBNETS"
|
||||||
env.RetroKeys("EXTRA_SUBNETS"))
|
outboundSubnetStrings := envToCSV(outboundSubnetsKey)
|
||||||
|
if len(outboundSubnetStrings) == 0 {
|
||||||
|
// Retro-compatibility
|
||||||
|
outboundSubnetStrings = envToCSV("EXTRA_SUBNETS")
|
||||||
|
if len(outboundSubnetStrings) > 0 {
|
||||||
|
outboundSubnetsKey = "EXTRA_SUBNETS"
|
||||||
|
r.onRetroActive("EXTRA_SUBNETS", "FIREWALL_OUTBOUND_SUBNETS")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
firewall.OutboundSubnets, err = stringsToIPNets(outboundSubnetStrings)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return firewall, err
|
return firewall, fmt.Errorf("environment variable %s: %w", outboundSubnetsKey, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
firewall.Enabled, err = s.env.BoolPtr("FIREWALL")
|
firewall.Enabled, err = envToBoolPtr("FIREWALL")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return firewall, err
|
return firewall, fmt.Errorf("environment variable FIREWALL: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
firewall.Debug, err = s.env.BoolPtr("FIREWALL_DEBUG")
|
firewall.Debug, err = envToBoolPtr("FIREWALL_DEBUG")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return firewall, err
|
return firewall, fmt.Errorf("environment variable FIREWALL_DEBUG: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return firewall, nil
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrIPNetParsing = errors.New("cannot parse IP network")
|
||||||
|
)
|
||||||
|
|
||||||
|
func stringsToIPNets(ss []string) (ipNets []net.IPNet, err error) {
|
||||||
|
if len(ss) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
ipNets = make([]net.IPNet, len(ss))
|
||||||
|
for i, s := range ss {
|
||||||
|
ip, ipNet, err := net.ParseCIDR(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%w: %s: %s",
|
||||||
|
ErrIPNetParsing, s, err)
|
||||||
|
}
|
||||||
|
ipNet.IP = ip
|
||||||
|
ipNets[i] = *ipNet
|
||||||
|
}
|
||||||
|
return ipNets, nil
|
||||||
|
}
|
||||||
|
|||||||
50
internal/configuration/sources/env/health.go
vendored
50
internal/configuration/sources/env/health.go
vendored
@@ -1,35 +1,55 @@
|
|||||||
package env
|
package env
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||||
"github.com/qdm12/gosettings/sources/env"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *Source) ReadHealth() (health settings.Health, err error) {
|
func (r *Reader) ReadHealth() (health settings.Health, err error) {
|
||||||
health.ServerAddress = s.env.String("HEALTH_SERVER_ADDRESS")
|
health.ServerAddress = os.Getenv("HEALTH_SERVER_ADDRESS")
|
||||||
health.TargetAddress = s.env.String("HEALTH_TARGET_ADDRESS",
|
health.TargetAddress = os.Getenv("HEALTH_ADDRESS_TO_PING")
|
||||||
env.RetroKeys("HEALTH_ADDRESS_TO_PING"))
|
if health.TargetAddress == "" {
|
||||||
|
health.TargetAddress = os.Getenv("HEALTH_TARGET_ADDRESS")
|
||||||
successWaitPtr, err := s.env.DurationPtr("HEALTH_SUCCESS_WAIT_DURATION")
|
|
||||||
if err != nil {
|
|
||||||
return health, err
|
|
||||||
} else if successWaitPtr != nil {
|
|
||||||
health.SuccessWait = *successWaitPtr
|
|
||||||
}
|
}
|
||||||
|
|
||||||
health.VPN.Initial, err = s.env.DurationPtr(
|
health.VPN.Initial, err = r.readDurationWithRetro(
|
||||||
"HEALTH_VPN_DURATION_INITIAL",
|
"HEALTH_VPN_DURATION_INITIAL",
|
||||||
env.RetroKeys("HEALTH_OPENVPN_DURATION_INITIAL"))
|
"HEALTH_OPENVPN_DURATION_INITIAL")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return health, err
|
return health, err
|
||||||
}
|
}
|
||||||
|
|
||||||
health.VPN.Addition, err = s.env.DurationPtr(
|
health.VPN.Initial, err = r.readDurationWithRetro(
|
||||||
"HEALTH_VPN_DURATION_ADDITION",
|
"HEALTH_VPN_DURATION_ADDITION",
|
||||||
env.RetroKeys("HEALTH_OPENVPN_DURATION_ADDITION"))
|
"HEALTH_OPENVPN_DURATION_ADDITION")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return health, err
|
return health, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return health, nil
|
return health, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Reader) readDurationWithRetro(envKey, retroEnvKey string) (d *time.Duration, err error) {
|
||||||
|
s := os.Getenv(envKey)
|
||||||
|
if s == "" {
|
||||||
|
s = os.Getenv(retroEnvKey)
|
||||||
|
if s == "" {
|
||||||
|
return nil, nil //nolint:nilnil
|
||||||
|
}
|
||||||
|
r.onRetroActive(envKey, retroEnvKey)
|
||||||
|
envKey = retroEnvKey
|
||||||
|
}
|
||||||
|
|
||||||
|
d = new(time.Duration)
|
||||||
|
*d, err = time.ParseDuration(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"environment variable %s: %w",
|
||||||
|
envKey, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return d, nil
|
||||||
|
}
|
||||||
|
|||||||
131
internal/configuration/sources/env/helpers.go
vendored
131
internal/configuration/sources/env/helpers.go
vendored
@@ -1,33 +1,134 @@
|
|||||||
package env
|
package env
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/qdm12/gosettings/sources/env"
|
"github.com/qdm12/govalid/binary"
|
||||||
|
"github.com/qdm12/govalid/integer"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func envToCSV(envKey string) (values []string) {
|
||||||
|
csv := os.Getenv(envKey)
|
||||||
|
if csv == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return lowerAndSplit(csv)
|
||||||
|
}
|
||||||
|
|
||||||
|
func envToStringPtr(envKey string) (stringPtr *string) {
|
||||||
|
s := os.Getenv(envKey)
|
||||||
|
if s == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &s
|
||||||
|
}
|
||||||
|
|
||||||
|
func envToBoolPtr(envKey string) (boolPtr *bool, err error) {
|
||||||
|
s := os.Getenv(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 := os.Getenv(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 := os.Getenv(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 := os.Getenv(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 := os.Getenv(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, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
var ErrDecodeBase64 = errors.New("cannot decode base64 string")
|
||||||
|
|
||||||
|
func decodeBase64(b64String string) (decoded string, err error) {
|
||||||
|
b, err := base64.StdEncoding.DecodeString(b64String)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("%w: %s: %s",
|
||||||
|
ErrDecodeBase64, b64String, err)
|
||||||
|
}
|
||||||
|
return string(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
func unsetEnvKeys(envKeys []string, err error) (newErr error) {
|
func unsetEnvKeys(envKeys []string, err error) (newErr error) {
|
||||||
newErr = err
|
newErr = err
|
||||||
for _, envKey := range envKeys {
|
for _, envKey := range envKeys {
|
||||||
unsetErr := os.Unsetenv(envKey)
|
unsetErr := os.Unsetenv(envKey)
|
||||||
if unsetErr != nil && newErr == nil {
|
if unsetErr != nil && newErr == nil {
|
||||||
newErr = fmt.Errorf("unsetting environment variable %s: %w", envKey, unsetErr)
|
newErr = fmt.Errorf("cannot unset environment variable %s: %w", envKey, unsetErr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return newErr
|
return newErr
|
||||||
}
|
}
|
||||||
|
|
||||||
func ptrTo[T any](value T) *T {
|
func stringPtr(s string) *string { return &s }
|
||||||
return &value
|
func uint16Ptr(n uint16) *uint16 { return &n }
|
||||||
}
|
func boolPtr(b bool) *bool { return &b }
|
||||||
|
|
||||||
func firstKeySet(e env.Env, keys ...string) (firstKeySet string) {
|
|
||||||
for _, key := range keys {
|
|
||||||
value := e.Get(key)
|
|
||||||
if value != nil {
|
|
||||||
return key
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|||||||
22
internal/configuration/sources/env/helpers_test.go
vendored
Normal file
22
internal/configuration/sources/env/helpers_test.go
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
184
internal/configuration/sources/env/httproxy.go
vendored
184
internal/configuration/sources/env/httproxy.go
vendored
@@ -2,37 +2,29 @@ package env
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||||
"github.com/qdm12/gosettings/sources/env"
|
|
||||||
"github.com/qdm12/govalid/binary"
|
"github.com/qdm12/govalid/binary"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *Source) readHTTPProxy() (httpProxy settings.HTTPProxy, err error) {
|
func (r *Reader) readHTTPProxy() (httpProxy settings.HTTPProxy, err error) {
|
||||||
httpProxy.User = s.env.Get("HTTPPROXY_USER",
|
httpProxy.User = r.readHTTProxyUser()
|
||||||
env.RetroKeys("PROXY_USER", "TINYPROXY_USER"),
|
httpProxy.Password = r.readHTTProxyPassword()
|
||||||
env.ForceLowercase(false))
|
httpProxy.ListeningAddress = r.readHTTProxyListeningAddress()
|
||||||
|
|
||||||
httpProxy.Password = s.env.Get("HTTPPROXY_PASSWORD",
|
httpProxy.Enabled, err = r.readHTTProxyEnabled()
|
||||||
env.RetroKeys("PROXY_PASSWORD", "TINYPROXY_PASSWORD"),
|
|
||||||
env.ForceLowercase(false))
|
|
||||||
|
|
||||||
httpProxy.ListeningAddress, err = s.readHTTProxyListeningAddress()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httpProxy, err
|
return httpProxy, err
|
||||||
}
|
}
|
||||||
|
|
||||||
httpProxy.Enabled, err = s.env.BoolPtr("HTTPPROXY", env.RetroKeys("PROXY", "TINYPROXY"))
|
httpProxy.Stealth, err = envToBoolPtr("HTTPPROXY_STEALTH")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httpProxy, err
|
return httpProxy, fmt.Errorf("environment variable HTTPPROXY_STEALTH: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
httpProxy.Stealth, err = s.env.BoolPtr("HTTPPROXY_STEALTH")
|
httpProxy.Log, err = r.readHTTProxyLog()
|
||||||
if err != nil {
|
|
||||||
return httpProxy, err
|
|
||||||
}
|
|
||||||
|
|
||||||
httpProxy.Log, err = s.readHTTProxyLog()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httpProxy, err
|
return httpProxy, err
|
||||||
}
|
}
|
||||||
@@ -40,45 +32,139 @@ func (s *Source) readHTTPProxy() (httpProxy settings.HTTPProxy, err error) {
|
|||||||
return httpProxy, nil
|
return httpProxy, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Source) readHTTProxyListeningAddress() (listeningAddress string, err error) {
|
func (r *Reader) readHTTProxyUser() (user *string) {
|
||||||
const currentKey = "HTTPPROXY_LISTENING_ADDRESS"
|
s := os.Getenv("HTTPPROXY_USER")
|
||||||
key := firstKeySet(s.env, "HTTPPROXY_PORT", "TINYPROXY_PORT", "PROXY_PORT",
|
if s != "" {
|
||||||
currentKey)
|
return &s
|
||||||
switch key {
|
|
||||||
case "":
|
|
||||||
return "", nil
|
|
||||||
case currentKey:
|
|
||||||
return s.env.String(key), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retro-compatible keys using a port only
|
// Retro-compatibility
|
||||||
s.handleDeprecatedKey(key, currentKey)
|
s = os.Getenv("TINYPROXY_USER")
|
||||||
port, err := s.env.Uint16Ptr(key)
|
if s != "" {
|
||||||
if err != nil {
|
r.onRetroActive("TINYPROXY_USER", "HTTPPROXY_USER")
|
||||||
return "", err
|
return &s
|
||||||
}
|
}
|
||||||
return fmt.Sprintf(":%d", *port), nil
|
|
||||||
|
// Retro-compatibility
|
||||||
|
s = os.Getenv("PROXY_USER")
|
||||||
|
if s != "" {
|
||||||
|
r.onRetroActive("PROXY_USER", "HTTPPROXY_USER")
|
||||||
|
return &s
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Source) readHTTProxyLog() (enabled *bool, err error) {
|
func (r *Reader) readHTTProxyPassword() (user *string) {
|
||||||
const currentKey = "HTTPPROXY_LOG"
|
s := os.Getenv("HTTPPROXY_PASSWORD")
|
||||||
key := firstKeySet(s.env, "PROXY_LOG", "TINYPROXY_LOG", "HTTPPROXY_LOG")
|
if s != "" {
|
||||||
switch key {
|
return &s
|
||||||
case "":
|
|
||||||
return nil, nil //nolint:nilnil
|
|
||||||
case currentKey:
|
|
||||||
return s.env.BoolPtr(key)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retro-compatible keys using different boolean verbs
|
// Retro-compatibility
|
||||||
s.handleDeprecatedKey(key, currentKey)
|
s = os.Getenv("TINYPROXY_PASSWORD")
|
||||||
value := s.env.String(key)
|
if s != "" {
|
||||||
|
r.onRetroActive("TINYPROXY_PASSWORD", "HTTPPROXY_PASSWORD")
|
||||||
|
return &s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retro-compatibility
|
||||||
|
s = os.Getenv("PROXY_PASSWORD")
|
||||||
|
if s != "" {
|
||||||
|
r.onRetroActive("PROXY_PASSWORD", "HTTPPROXY_PASSWORD")
|
||||||
|
return &s
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) readHTTProxyListeningAddress() (listeningAddress string) {
|
||||||
|
// Retro-compatibility
|
||||||
|
retroKeys := []string{"PROXY_PORT", "TINYPROXY_PORT", "HTTPPROXY_PORT"}
|
||||||
|
for _, retroKey := range retroKeys {
|
||||||
|
s := os.Getenv(retroKey)
|
||||||
|
if s != "" {
|
||||||
|
r.onRetroActive(retroKey, "HTTPPROXY_LISTENING_ADDRESS")
|
||||||
|
return ":" + s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return os.Getenv("HTTPPROXY_LISTENING_ADDRESS")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) readHTTProxyEnabled() (enabled *bool, err error) {
|
||||||
|
s := strings.ToLower(os.Getenv("HTTPPROXY"))
|
||||||
|
if s != "" {
|
||||||
|
enabled = new(bool)
|
||||||
|
*enabled, err = binary.Validate(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("environment variable HTTPPROXY: %w", err)
|
||||||
|
}
|
||||||
|
return enabled, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retro-compatibility
|
||||||
|
s = strings.ToLower(os.Getenv("TINYPROXY"))
|
||||||
|
if s != "" {
|
||||||
|
r.onRetroActive("TINYPROXY", "HTTPPROXY")
|
||||||
|
enabled = new(bool)
|
||||||
|
*enabled, err = binary.Validate(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("environment variable TINYPROXY: %w", err)
|
||||||
|
}
|
||||||
|
return enabled, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retro-compatibility
|
||||||
|
s = strings.ToLower(os.Getenv("PROXY"))
|
||||||
|
if s != "" {
|
||||||
|
r.onRetroActive("PROXY", "HTTPPROXY")
|
||||||
|
enabled = new(bool)
|
||||||
|
*enabled, err = binary.Validate(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("environment variable PROXY: %w", err)
|
||||||
|
}
|
||||||
|
return enabled, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil //nolint:nilnil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) readHTTProxyLog() (enabled *bool, err error) {
|
||||||
|
s := strings.ToLower(os.Getenv("HTTPPROXY_LOG"))
|
||||||
|
if s != "" {
|
||||||
|
enabled = new(bool)
|
||||||
|
*enabled, err = binary.Validate(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("environment variable HTTPPROXY_LOG: %w", err)
|
||||||
|
}
|
||||||
|
return enabled, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retro-compatibility
|
||||||
retroOption := binary.OptionEnabled("on", "info", "connect", "notice")
|
retroOption := binary.OptionEnabled("on", "info", "connect", "notice")
|
||||||
|
s = strings.ToLower(os.Getenv("TINYPROXY_LOG"))
|
||||||
enabled, err = binary.Validate(value, retroOption)
|
if s != "" {
|
||||||
if err != nil {
|
r.onRetroActive("TINYPROXY_LOG", "HTTPPROXY_LOG")
|
||||||
return nil, fmt.Errorf("environment variable %s: %w", key, err)
|
enabled = new(bool)
|
||||||
|
*enabled, err = binary.Validate(s, retroOption)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("environment variable TINYPROXY_LOG: %w", err)
|
||||||
|
}
|
||||||
|
return enabled, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return enabled, nil
|
// Retro-compatibility
|
||||||
|
s = strings.ToLower(os.Getenv("PROXY_LOG_LEVEL"))
|
||||||
|
if s != "" {
|
||||||
|
r.onRetroActive("PROXY_LOG_LEVEL", "HTTPPROXY_LOG")
|
||||||
|
enabled = new(bool)
|
||||||
|
*enabled, err = binary.Validate(s, retroOption)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("environment variable PROXY_LOG_LEVEL: %w", err)
|
||||||
|
}
|
||||||
|
return enabled, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil //nolint:nilnil
|
||||||
}
|
}
|
||||||
|
|||||||
29
internal/configuration/sources/env/log.go
vendored
29
internal/configuration/sources/env/log.go
vendored
@@ -3,14 +3,15 @@ package env
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||||
"github.com/qdm12/log"
|
"github.com/qdm12/golibs/logging"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *Source) readLog() (log settings.Log, err error) {
|
func readLog() (log settings.Log, err error) {
|
||||||
log.Level, err = s.readLogLevel()
|
log.Level, err = readLogLevel()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return log, err
|
return log, err
|
||||||
}
|
}
|
||||||
@@ -18,14 +19,14 @@ func (s *Source) readLog() (log settings.Log, err error) {
|
|||||||
return log, nil
|
return log, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Source) readLogLevel() (level *log.Level, err error) {
|
func readLogLevel() (level *logging.Level, err error) {
|
||||||
value := s.env.String("LOG_LEVEL")
|
s := os.Getenv("LOG_LEVEL")
|
||||||
if value == "" {
|
if s == "" {
|
||||||
return nil, nil //nolint:nilnil
|
return nil, nil //nolint:nilnil
|
||||||
}
|
}
|
||||||
|
|
||||||
level = new(log.Level)
|
level = new(logging.Level)
|
||||||
*level, err = parseLogLevel(value)
|
*level, err = parseLogLevel(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("environment variable LOG_LEVEL: %w", err)
|
return nil, fmt.Errorf("environment variable LOG_LEVEL: %w", err)
|
||||||
}
|
}
|
||||||
@@ -35,19 +36,19 @@ func (s *Source) readLogLevel() (level *log.Level, err error) {
|
|||||||
|
|
||||||
var ErrLogLevelUnknown = errors.New("log level is unknown")
|
var ErrLogLevelUnknown = errors.New("log level is unknown")
|
||||||
|
|
||||||
func parseLogLevel(s string) (level log.Level, err error) {
|
func parseLogLevel(s string) (level logging.Level, err error) {
|
||||||
switch strings.ToLower(s) {
|
switch strings.ToLower(s) {
|
||||||
case "debug":
|
case "debug":
|
||||||
return log.LevelDebug, nil
|
return logging.LevelDebug, nil
|
||||||
case "info":
|
case "info":
|
||||||
return log.LevelInfo, nil
|
return logging.LevelInfo, nil
|
||||||
case "warning":
|
case "warning":
|
||||||
return log.LevelWarn, nil
|
return logging.LevelWarn, nil
|
||||||
case "error":
|
case "error":
|
||||||
return log.LevelError, nil
|
return logging.LevelError, nil
|
||||||
default:
|
default:
|
||||||
return level, fmt.Errorf(
|
return level, fmt.Errorf(
|
||||||
"%w: %q is not valid and can be one of debug, info, warning or error",
|
"%w: %s: can be one of: debug, info, warning or error",
|
||||||
ErrLogLevelUnknown, s)
|
ErrLogLevelUnknown, s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
149
internal/configuration/sources/env/openvpn.go
vendored
149
internal/configuration/sources/env/openvpn.go
vendored
@@ -1,77 +1,130 @@
|
|||||||
package env
|
package env
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||||
"github.com/qdm12/gosettings/sources/env"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *Source) readOpenVPN() (
|
func (r *Reader) readOpenVPN() (
|
||||||
openVPN settings.OpenVPN, err error) {
|
openVPN settings.OpenVPN, err error) {
|
||||||
defer func() {
|
defer func() {
|
||||||
err = unsetEnvKeys([]string{"OPENVPN_KEY", "OPENVPN_CERT",
|
err = unsetEnvKeys([]string{"OPENVPN_CLIENTKEY", "OPENVPN_CLIENTCRT"}, err)
|
||||||
"OPENVPN_KEY_PASSPHRASE", "OPENVPN_ENCRYPTED_KEY"}, err)
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
openVPN.Version = s.env.String("OPENVPN_VERSION")
|
openVPN.Version = os.Getenv("OPENVPN_VERSION")
|
||||||
openVPN.User = s.env.Get("OPENVPN_USER",
|
openVPN.User = r.readOpenVPNUser()
|
||||||
env.RetroKeys("USER"), env.ForceLowercase(false))
|
openVPN.Password = r.readOpenVPNPassword()
|
||||||
openVPN.Password = s.env.Get("OPENVPN_PASSWORD",
|
confFile := os.Getenv("OPENVPN_CUSTOM_CONFIG")
|
||||||
env.RetroKeys("PASSWORD"), env.ForceLowercase(false))
|
if confFile != "" {
|
||||||
openVPN.ConfFile = s.env.Get("OPENVPN_CUSTOM_CONFIG", env.ForceLowercase(false))
|
openVPN.ConfFile = &confFile
|
||||||
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 = s.env.Uint16Ptr("OPENVPN_MSSFIX")
|
|
||||||
if err != nil {
|
|
||||||
return openVPN, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
openVPN.Interface = s.env.String("VPN_INTERFACE",
|
openVPN.Ciphers = envToCSV("OPENVPN_CIPHER")
|
||||||
env.RetroKeys("OPENVPN_INTERFACE"), env.ForceLowercase(false))
|
auth := os.Getenv("OPENVPN_AUTH")
|
||||||
|
if auth != "" {
|
||||||
openVPN.ProcessUser, err = s.readOpenVPNProcessUser()
|
openVPN.Auth = &auth
|
||||||
if err != nil {
|
|
||||||
return openVPN, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
openVPN.Verbosity, err = s.env.IntPtr("OPENVPN_VERBOSITY")
|
openVPN.ClientCrt, err = readBase64OrNil("OPENVPN_CLIENTCRT")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return openVPN, err
|
return openVPN, fmt.Errorf("environment variable OPENVPN_CLIENTCRT: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
flagsPtr := s.env.Get("OPENVPN_FLAGS", env.ForceLowercase(false))
|
openVPN.ClientKey, err = readBase64OrNil("OPENVPN_CLIENTKEY")
|
||||||
if flagsPtr != nil {
|
if err != nil {
|
||||||
openVPN.Flags = strings.Fields(*flagsPtr)
|
return openVPN, fmt.Errorf("environment variable OPENVPN_CLIENTKEY: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
openVPN.PIAEncPreset = r.readPIAEncryptionPreset()
|
||||||
|
|
||||||
|
openVPN.IPv6, err = envToBoolPtr("OPENVPN_IPV6")
|
||||||
|
if err != nil {
|
||||||
|
return openVPN, fmt.Errorf("environment variable OPENVPN_IPV6: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
openVPN.MSSFix, err = envToUint16Ptr("OPENVPN_MSSFIX")
|
||||||
|
if err != nil {
|
||||||
|
return openVPN, fmt.Errorf("environment variable OPENVPN_MSSFIX: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
openVPN.Interface = os.Getenv("OPENVPN_INTERFACE")
|
||||||
|
|
||||||
|
openVPN.Root, err = envToBoolPtr("OPENVPN_ROOT")
|
||||||
|
if err != nil {
|
||||||
|
return openVPN, fmt.Errorf("environment variable OPENVPN_ROOT: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO ProcUser once Root is deprecated.
|
||||||
|
|
||||||
|
openVPN.Verbosity, err = envToIntPtr("OPENVPN_VERBOSITY")
|
||||||
|
if err != nil {
|
||||||
|
return openVPN, fmt.Errorf("environment variable OPENVPN_VERBOSITY: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
flagsStr := os.Getenv("OPENVPN_FLAGS")
|
||||||
|
if flagsStr != "" {
|
||||||
|
openVPN.Flags = strings.Fields(flagsStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
return openVPN, nil
|
return openVPN, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Source) readPIAEncryptionPreset() (presetPtr *string) {
|
func (r *Reader) readOpenVPNUser() (user string) {
|
||||||
return s.env.Get(
|
user = os.Getenv("OPENVPN_USER")
|
||||||
"PRIVATE_INTERNET_ACCESS_OPENVPN_ENCRYPTION_PRESET",
|
if user == "" {
|
||||||
env.RetroKeys("ENCRYPTION", "PIA_ENCRYPTION"))
|
// Retro-compatibility
|
||||||
|
user = os.Getenv("USER")
|
||||||
|
if user != "" {
|
||||||
|
r.onRetroActive("USER", "OPENVPN_USER")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Remove spaces in user ID to simplify user's life, thanks @JeordyR
|
||||||
|
return strings.ReplaceAll(user, " ", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Source) readOpenVPNProcessUser() (processUser string, err error) {
|
func (r *Reader) readOpenVPNPassword() (password string) {
|
||||||
value, err := s.env.BoolPtr("OPENVPN_ROOT") // Retro-compatibility
|
password = os.Getenv("OPENVPN_PASSWORD")
|
||||||
if err != nil {
|
if password != "" {
|
||||||
return "", err
|
return password
|
||||||
} else if value != nil {
|
|
||||||
if *value {
|
|
||||||
return "root", nil
|
|
||||||
}
|
|
||||||
const defaultNonRootUser = "nonrootuser"
|
|
||||||
return defaultNonRootUser, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.env.String("OPENVPN_PROCESS_USER"), nil
|
// Retro-compatibility
|
||||||
|
password = os.Getenv("PASSWORD")
|
||||||
|
if password != "" {
|
||||||
|
r.onRetroActive("PASSWORD", "OPENVPN_PASSWORD")
|
||||||
|
}
|
||||||
|
return password
|
||||||
|
}
|
||||||
|
|
||||||
|
func readBase64OrNil(envKey string) (valueOrNil *string, err error) {
|
||||||
|
value := os.Getenv(envKey)
|
||||||
|
if value == "" {
|
||||||
|
return nil, nil //nolint:nilnil
|
||||||
|
}
|
||||||
|
|
||||||
|
decoded, err := decodeBase64(value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &decoded, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) readPIAEncryptionPreset() (presetPtr *string) {
|
||||||
|
preset := strings.ToLower(os.Getenv("PIA_ENCRYPTION"))
|
||||||
|
if preset != "" {
|
||||||
|
return &preset
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retro-compatibility
|
||||||
|
preset = strings.ToLower(os.Getenv("ENCRYPTION"))
|
||||||
|
if preset != "" {
|
||||||
|
r.onRetroActive("ENCRYPTION", "PIA_ENCRYPTION")
|
||||||
|
return &preset
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,54 +3,81 @@ package env
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
"github.com/qdm12/gosettings/sources/env"
|
"github.com/qdm12/govalid/port"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *Source) readOpenVPNSelection() (
|
func (r *Reader) readOpenVPNSelection() (
|
||||||
selection settings.OpenVPNSelection, err error) {
|
selection settings.OpenVPNSelection, err error) {
|
||||||
selection.ConfFile = s.env.Get("OPENVPN_CUSTOM_CONFIG", env.ForceLowercase(false))
|
confFile := os.Getenv("OPENVPN_CUSTOM_CONFIG")
|
||||||
|
if confFile != "" {
|
||||||
|
selection.ConfFile = &confFile
|
||||||
|
}
|
||||||
|
|
||||||
selection.TCP, err = s.readOpenVPNProtocol()
|
selection.TCP, err = r.readOpenVPNProtocol()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return selection, err
|
return selection, err
|
||||||
}
|
}
|
||||||
|
|
||||||
selection.CustomPort, err = s.env.Uint16Ptr("VPN_ENDPOINT_PORT",
|
selection.CustomPort, err = r.readOpenVPNCustomPort()
|
||||||
env.RetroKeys("PORT", "OPENVPN_PORT"))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return selection, err
|
return selection, err
|
||||||
}
|
}
|
||||||
|
|
||||||
selection.PIAEncPreset = s.readPIAEncryptionPreset()
|
selection.PIAEncPreset = r.readPIAEncryptionPreset()
|
||||||
|
|
||||||
return selection, nil
|
return selection, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var ErrOpenVPNProtocolNotValid = errors.New("OpenVPN protocol is not valid")
|
var ErrOpenVPNProtocolNotValid = errors.New("OpenVPN protocol is not valid")
|
||||||
|
|
||||||
func (s *Source) readOpenVPNProtocol() (tcp *bool, err error) {
|
func (r *Reader) readOpenVPNProtocol() (tcp *bool, err error) {
|
||||||
const currentKey = "OPENVPN_PROTOCOL"
|
envKey := "OPENVPN_PROTOCOL"
|
||||||
envKey := firstKeySet(s.env, "PROTOCOL", currentKey)
|
protocol := strings.ToLower(os.Getenv("OPENVPN_PROTOCOL"))
|
||||||
switch envKey {
|
if protocol == "" {
|
||||||
case "":
|
// Retro-compatibility
|
||||||
return nil, nil //nolint:nilnil
|
protocol = strings.ToLower(os.Getenv("PROTOCOL"))
|
||||||
case currentKey:
|
if protocol != "" {
|
||||||
default: // Retro compatibility
|
envKey = "PROTOCOL"
|
||||||
s.handleDeprecatedKey(envKey, currentKey)
|
r.onRetroActive("PROTOCOL", "OPENVPN_PROTOCOL")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protocol := s.env.String(envKey)
|
switch protocol {
|
||||||
switch strings.ToLower(protocol) {
|
case "":
|
||||||
|
return nil, nil //nolint:nilnil
|
||||||
case constants.UDP:
|
case constants.UDP:
|
||||||
return ptrTo(false), nil
|
return boolPtr(false), nil
|
||||||
case constants.TCP:
|
case constants.TCP:
|
||||||
return ptrTo(true), nil
|
return boolPtr(true), nil
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("environment variable %s: %w: %s",
|
return nil, fmt.Errorf("environment variable %s: %w: %s",
|
||||||
envKey, ErrOpenVPNProtocolNotValid, protocol)
|
envKey, ErrOpenVPNProtocolNotValid, protocol)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Reader) readOpenVPNCustomPort() (customPort *uint16, err error) {
|
||||||
|
key := "OPENVPN_PORT"
|
||||||
|
s := os.Getenv(key)
|
||||||
|
if s == "" {
|
||||||
|
// Retro-compatibility
|
||||||
|
key = "PORT"
|
||||||
|
s = os.Getenv(key)
|
||||||
|
if s == "" {
|
||||||
|
return nil, nil //nolint:nilnil
|
||||||
|
}
|
||||||
|
r.onRetroActive("PORT", "OPENVPN_PORT")
|
||||||
|
}
|
||||||
|
|
||||||
|
customPort = new(uint16)
|
||||||
|
*customPort, err = port.Validate(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("environment variable %s: %w", key, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return customPort, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,34 +1,19 @@
|
|||||||
package env
|
package env
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||||
"github.com/qdm12/gosettings/sources/env"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *Source) readPortForward() (
|
func readPortForward() (
|
||||||
portForwarding settings.PortForwarding, err error) {
|
portForwarding settings.PortForwarding, err error) {
|
||||||
portForwarding.Enabled, err = s.env.BoolPtr("VPN_PORT_FORWARDING",
|
portForwarding.Enabled, err = envToBoolPtr("PORT_FORWARDING")
|
||||||
env.RetroKeys(
|
|
||||||
"PORT_FORWARDING",
|
|
||||||
"PRIVATE_INTERNET_ACCESS_VPN_PORT_FORWARDING",
|
|
||||||
))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return portForwarding, err
|
return portForwarding, fmt.Errorf("environment variable PORT_FORWARDING: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
portForwarding.Provider = s.env.Get("VPN_PORT_FORWARDING_PROVIDER")
|
portForwarding.Filepath = envToStringPtr("PORT_FORWARDING_STATUS_FILE")
|
||||||
|
|
||||||
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",
|
|
||||||
))
|
|
||||||
|
|
||||||
portForwarding.ListeningPort, err = s.env.Uint16Ptr("VPN_PORT_FORWARDING_LISTENING_PORT")
|
|
||||||
if err != nil {
|
|
||||||
return portForwarding, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return portForwarding, nil
|
return portForwarding, nil
|
||||||
}
|
}
|
||||||
|
|||||||
26
internal/configuration/sources/env/pprof.go
vendored
26
internal/configuration/sources/env/pprof.go
vendored
@@ -1,26 +0,0 @@
|
|||||||
package env
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/qdm12/gluetun/internal/pprof"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (s *Source) readPprof() (settings pprof.Settings, err error) {
|
|
||||||
settings.Enabled, err = s.env.BoolPtr("PPROF_ENABLED")
|
|
||||||
if err != nil {
|
|
||||||
return settings, err
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.BlockProfileRate, err = s.env.IntPtr("PPROF_BLOCK_PROFILE_RATE")
|
|
||||||
if err != nil {
|
|
||||||
return settings, err
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.MutexProfileRate, err = s.env.IntPtr("PPROF_MUTEX_PROFILE_RATE")
|
|
||||||
if err != nil {
|
|
||||||
return settings, err
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.HTTPServer.Address = s.env.String("PPROF_HTTP_SERVER_ADDRESS")
|
|
||||||
|
|
||||||
return settings, nil
|
|
||||||
}
|
|
||||||
42
internal/configuration/sources/env/provider.go
vendored
42
internal/configuration/sources/env/provider.go
vendored
@@ -2,49 +2,43 @@ package env
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
"github.com/qdm12/gluetun/internal/constants/vpn"
|
|
||||||
"github.com/qdm12/gosettings/sources/env"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *Source) readProvider(vpnType string) (provider settings.Provider, err error) {
|
func (r *Reader) readProvider(vpnType string) (provider settings.Provider, err error) {
|
||||||
provider.Name = s.readVPNServiceProvider(vpnType)
|
provider.Name = readVPNServiceProvider(vpnType)
|
||||||
var providerName string
|
var providerName string
|
||||||
if provider.Name != nil {
|
if provider.Name != nil {
|
||||||
providerName = *provider.Name
|
providerName = *provider.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
provider.ServerSelection, err = s.readServerSelection(providerName, vpnType)
|
provider.ServerSelection, err = r.readServerSelection(providerName, vpnType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return provider, fmt.Errorf("server selection: %w", err)
|
return provider, fmt.Errorf("cannot read server selection settings: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
provider.PortForwarding, err = s.readPortForward()
|
provider.PortForwarding, err = readPortForward()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return provider, fmt.Errorf("port forwarding: %w", err)
|
return provider, fmt.Errorf("cannot read port forwarding settings: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return provider, nil
|
return provider, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Source) readVPNServiceProvider(vpnType string) (vpnProviderPtr *string) {
|
func readVPNServiceProvider(vpnType string) (vpnProviderPtr *string) {
|
||||||
valuePtr := s.env.Get("VPN_SERVICE_PROVIDER", env.RetroKeys("VPNSP"))
|
s := strings.ToLower(os.Getenv("VPNSP"))
|
||||||
if valuePtr == nil {
|
switch {
|
||||||
if vpnType != vpn.Wireguard && s.env.Get("OPENVPN_CUSTOM_CONFIG") != nil {
|
case vpnType != constants.Wireguard &&
|
||||||
// retro compatibility
|
os.Getenv("OPENVPN_CUSTOM_CONFIG") != "": // retro compatibility
|
||||||
return ptrTo(providers.Custom)
|
return stringPtr(constants.Custom)
|
||||||
}
|
case s == "":
|
||||||
return nil
|
return nil
|
||||||
|
case s == "pia": // retro compatibility
|
||||||
|
return stringPtr(constants.PrivateInternetAccess)
|
||||||
}
|
}
|
||||||
|
return stringPtr(s)
|
||||||
value := *valuePtr
|
|
||||||
value = strings.ToLower(value)
|
|
||||||
if value == "pia" { // retro compatibility
|
|
||||||
return ptrTo(providers.PrivateInternetAccess)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ptrTo(value)
|
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user