Compare commits

..

20 Commits

Author SHA1 Message Date
Quentin McGaw
d0f5d0c007 fix(version): log last release by tag name alphabetically instead of date 2024-08-09 07:46:19 +00:00
dependabot[bot]
06949554dd Chore(deps): Bump github.com/breml/rootcerts from 0.2.16 to 0.2.17 (#2316) 2024-08-09 07:29:49 +00:00
Quentin McGaw
8e8cbe0d37 fix(privatevpn): set openvpn vpn type for no hostname server 2024-08-09 07:29:22 +00:00
Quentin McGaw
3d72f6092b fix(firewall): VPN_PORT_FORWARDING_LISTENING_PORT behavior fixed
by not restricting the destination address to 127.0.0.1
2024-08-09 07:29:22 +00:00
Quentin McGaw
0ac047f9de fix(format-servers): add missing vpn type column for natively supported providers
- nordvpn
- surfshark
2024-08-09 07:29:22 +00:00
Quentin McGaw
129002a058 fix(custom-openvpn): remove comments before parsing file 2024-08-09 07:29:22 +00:00
Quentin McGaw
e95095ad5c fix(config): log out if port forwarding only servers is enabled 2024-08-09 07:29:22 +00:00
Quentin McGaw
efecb0ea0e fix(publicip): abort ip data fetch if vpn context is canceled
- Prevents requesting the public IP address N times after N VPN failures
- Fetching runs with a context local to the 'single run'
- Single run writes single run result to a channel back to the caller, RunOnce is now blocking
2024-08-09 07:29:22 +00:00
Quentin McGaw
b504227625 fix(ipv6): detect ignoring loopback route destinations 2024-08-09 07:29:22 +00:00
Quentin McGaw
2b298de59d fix(torguard): update OpenVPN configuration
- add aes-128-gcm and aes-128-cbc ciphers
- add extra CA certificate value
- remove mssfix option
- remove sndbuf and rcvbuf ption
- remove ping option
- remove reneg option
2024-08-09 07:29:22 +00:00
Quentin McGaw
3d4c4e505a fix(custom): do not set server name if it does not exist 2024-08-09 07:29:22 +00:00
Quentin McGaw
948ebdc4e1 fix(vpnunlimited): allow OpenVPN TCP on port 1197 2024-08-09 07:29:22 +00:00
Quentin McGaw
7cf46a0ac3 fix(vpnunlimited): change UDP port from 1194 to 1197 2024-08-09 07:29:22 +00:00
Quentin McGaw
faa8340eea fix(custom): parse port option line for OpenVPN 2024-08-09 07:29:22 +00:00
Quentin McGaw
8267d0e3bd fix(custom): set server name if names filter is not empty
- fix PIA port forwarding code usage
- refers to #2147
2024-08-09 07:29:22 +00:00
Quentin McGaw
f04ac56454 feat(airvpn): set default mssfix to 1320-28 2024-08-09 07:29:22 +00:00
Quentin McGaw
d9997f5234 chore(vpnsecure): associate "N / A" with no data for servers 2024-08-09 07:29:22 +00:00
Quentin McGaw
ac5c13e9c9 fix(surfshark): remove outdated hardcoded retro servers 2024-08-09 07:29:22 +00:00
Quentin McGaw
7570a79ce6 fix(tun): only create tun device if it does not exist
- do not create if it exists and is problematic
- wrap errors with a better context
2024-08-09 07:29:22 +00:00
Quentin McGaw
c47858fdda fix(server): /openvpn route status get and put
- get status return stopped if running wireguard
- put status changes vpn type if running wireguard
2024-08-09 07:29:22 +00:00
541 changed files with 16845 additions and 35484 deletions

View File

@@ -1,4 +1,5 @@
.dockerignore .dockerignore
devcontainer.json devcontainer.json
docker-compose.yml
Dockerfile Dockerfile
README.md README.md

View File

@@ -1,2 +1,2 @@
FROM qmcgaw/godevcontainer:v0.20-alpine FROM qmcgaw/godevcontainer
RUN apk add wireguard-tools htop openssl RUN apk add wireguard-tools htop openssl

View File

@@ -2,47 +2,68 @@
Development container that can be used with VSCode. Development container that can be used with VSCode.
It works on Linux, Windows (WSL2) and OSX. It works on Linux, Windows and OSX.
## Requirements ## Requirements
- [VS code](https://code.visualstudio.com/download) installed - [VS code](https://code.visualstudio.com/download) installed
- [VS code dev 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
- [Docker Compose](https://docs.docker.com/compose/install/) installed
## Setup ## Setup
1. Create the following files and directory on your host if you don't have them: 1. Create the following files on your host if you don't have them:
```sh ```sh
touch ~/.gitconfig ~/.zsh_history touch ~/.gitconfig ~/.zsh_history
mkdir -p ~/.ssh
``` ```
1. **For Docker on OSX**: ensure the project directory and your home directory `~` are accessible by Docker. 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 `Dev Containers: Open Folder in Container...` and choose the project directory. 1. Select `Remote-Containers: Open Folder in Container...` and choose the project directory.
## Customization ## Customization
For any customization to take effect, you should "rebuild and reopen": ### Customize the image
1. Open the command palette in Visual Studio Code (CTRL+SHIFT+P) You can make changes to the [Dockerfile](Dockerfile) and then rebuild the image. For example, your Dockerfile could be:
2. Select `Dev Containers: Rebuild Container`
Changes you can make are notably: ```Dockerfile
FROM qmcgaw/godevcontainer
RUN apk add curl
```
- Changes to the Docker image in [Dockerfile](Dockerfile) To rebuild the image, either:
- Changes to VSCode **settings** and **extensions** in [devcontainer.json](devcontainer.json).
- Change the entrypoint script by adding a bind mount in [devcontainer.json](devcontainer.json) of a shell script to `/root/.welcome.sh` to replace the [current welcome script](https://github.com/qdm12/godevcontainer/blob/master/shell/.welcome.sh). For example:
```json - With VSCode through the command palette, select `Remote-Containers: Rebuild and reopen in container`
// Welcome script - With a terminal, go to this directory and `docker-compose build`
{
"source": "/yourpath/.welcome.sh", ### Customize VS code settings
"target": "/root/.welcome.sh",
"type": "bind" You can customize **settings** and **extensions** in the [devcontainer.json](devcontainer.json) definition file.
},
### 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).
### 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.
### Run other services
1. Modify [docker-compose.yml](docker-compose.yml) to launch other services at the same time as this development container, such as a test database:
```yml
database:
image: postgres
restart: always
environment:
POSTGRES_PASSWORD: password
``` ```
- More options are documented in the [devcontainer.json reference](https://containers.dev/implementors/json_reference/). 1. In [devcontainer.json](devcontainer.json), change the line `"runServices": ["vscode"],` to `"runServices": ["vscode", "database"],`.
1. In the VS code command palette, rebuild the container.

View File

@@ -1,50 +1,16 @@
{ {
"name": "gluetun-dev", "name": "gluetun-dev",
// User defined settings "dockerComposeFile": [
"containerEnv": { "docker-compose.yml"
"TZ": ""
},
// Fixed settings
"build": {
"dockerfile": "./Dockerfile"
},
"postCreateCommand": "~/.windows.sh && go mod download",
"capAdd": [
"NET_ADMIN", // Gluetun specific
"SYS_PTRACE" // for dlv Go debugging
], ],
"securityOpt": [ "service": "vscode",
"seccomp=unconfined" // for dlv Go debugging "runServices": [
], "vscode"
"mounts": [
// Zsh commands history persistence
{
"source": "${localEnv:HOME}/.zsh_history",
"target": "/root/.zsh_history",
"type": "bind"
},
// Git configuration file
{
"source": "${localEnv:HOME}/.gitconfig",
"target": "/root/.gitconfig",
"type": "bind"
},
// SSH directory for Linux, OSX and WSL
// On Linux and OSX, a symlink /mnt/ssh <-> ~/.ssh is
// created in the container. On Windows, files are copied
// from /mnt/ssh to ~/.ssh to fix permissions.
{
"source": "${localEnv:HOME}/.ssh",
"target": "/mnt/ssh",
"type": "bind"
},
// Docker socket to access the host Docker server
{
"source": "/var/run/docker.sock",
"target": "/var/run/docker.sock",
"type": "bind"
}
], ],
"shutdownAction": "stopCompose",
"postCreateCommand": "~/.windows.sh && go mod download && go mod tidy",
"workspaceFolder": "/workspace",
// "overrideCommand": "",
"customizations": { "customizations": {
"vscode": { "vscode": {
"extensions": [ "extensions": [
@@ -71,18 +37,17 @@
"go.useLanguageServer": true, "go.useLanguageServer": true,
"[go]": { "[go]": {
"editor.codeActionsOnSave": { "editor.codeActionsOnSave": {
"source.organizeImports": "explicit" "source.organizeImports": true
} }
}, },
"[go.mod]": { "[go.mod]": {
"editor.codeActionsOnSave": { "editor.codeActionsOnSave": {
"source.organizeImports": "explicit" "source.organizeImports": true
} }
}, },
"gopls": { "gopls": {
"usePlaceholders": false, "usePlaceholders": false,
"staticcheck": true, "staticcheck": true
"formatting.gofumpt": true,
}, },
"go.lintTool": "golangci-lint", "go.lintTool": "golangci-lint",
"go.lintOnSave": "package", "go.lintOnSave": "package",

View File

@@ -0,0 +1,28 @@
version: "3.7"
services:
vscode:
build: .
volumes:
- ../:/workspace
# Docker socket to access Docker server
- /var/run/docker.sock:/var/run/docker.sock
# SSH directory for Linux, OSX and WSL
# On Linux and OSX, a symlink /mnt/ssh <-> ~/.ssh is
# created in the container. On Windows, files are copied
# from /mnt/ssh to ~/.ssh to fix permissions.
- ~/.ssh:/mnt/ssh
# Shell history persistence
- ~/.zsh_history:/root/.zsh_history
# Git config
- ~/.gitconfig:/root/.gitconfig
environment:
- TZ=
cap_add:
# For debugging with dlv
- SYS_PTRACE
- NET_ADMIN
security_opt:
# For debugging with dlv
- seccomp:unconfined
entrypoint: [ "zsh", "-c", "while sleep 1000; do :; done" ]

View File

@@ -50,7 +50,6 @@ body:
- Cyberghost - Cyberghost
- ExpressVPN - ExpressVPN
- FastestVPN - FastestVPN
- Giganews
- HideMyAss - HideMyAss
- IPVanish - IPVanish
- IVPN - IVPN

View File

@@ -1,10 +1,9 @@
blank_issues_enabled: false
contact_links: contact_links:
- name: Report a Wiki issue - name: Report a Wiki issue
url: https://github.com/qdm12/gluetun-wiki/issues/new/choose url: https://github.com/qdm12/gluetun-wiki/issues/new
about: Please create an issue on the gluetun-wiki repository. about: Please create an issue on the gluetun-wiki repository.
- name: Configuration help? - name: Configuration help?
url: https://github.com/qdm12/gluetun/discussions/new/choose url: https://github.com/qdm12/gluetun/discussions/new
about: Please create a Github discussion. about: Please create a Github discussion.
- name: Unraid template issue - name: Unraid template issue
url: https://github.com/qdm12/gluetun/discussions/550 url: https://github.com/qdm12/gluetun/discussions/550

201
.github/labels.yml vendored
View File

@@ -1,152 +1,133 @@
- name: "Status: 🗯️ Waiting for feedback" # Temporary status
color: "f7d692" - name: "🗯️ Waiting for feedback"
- name: "Status: 🔴 Blocked" color: "aadefa"
color: "f7d692" description: ""
- name: "🔴 Blocked"
color: "ff3f14"
description: "Blocked by another issue or pull request" description: "Blocked by another issue or pull request"
- name: "Status: 📌 Before next release" - name: "🔒 After next release"
color: "f7d692" color: "e8f274"
description: "Has to be done before the next release"
- name: "Status: 🔒 After next release"
color: "f7d692"
description: "Will be done after the next release" description: "Will be done after the next release"
- name: "Status: 🟡 Nearly resolved"
color: "f7d692"
description: "This might be resolved or is about to be resolved"
- name: "Closed: ⚰️ Inactive" # Priority
color: "959a9c" - name: "🚨 Urgent"
description: "No answer was received for weeks" color: "d5232f"
- name: "Closed: 👥 Duplicate" description: ""
color: "959a9c" - name: "💤 Low priority"
description: "Issue duplicates an existing issue" color: "4285f4"
- name: "Closed: 🗑️ Bad issue" description: ""
color: "959a9c"
- name: "Closed: ☠️ cannot be done"
color: "959a9c"
- name: "Priority: 🚨 Urgent" # Complexity
color: "03adfc" - name: "☣️ Hard to do"
- name: "Priority: 💤 Low priority" color: "7d0008"
color: "03adfc" description: ""
- name: "🟩 Easy to do"
- name: "Complexity: ☣️ Hard to do" color: "34cf43"
color: "ff9efc" description: ""
- name: "Complexity: 🟩 Easy to do"
color: "ff9efc"
- name: "Popularity: ❤️‍🔥 extreme"
color: "ffc7ea"
- name: "Popularity: ❤️ high"
color: "ffc7ea"
# VPN providers # VPN providers
- name: "☁️ AirVPN" - name: ":cloud: AirVPN"
color: "cfe8d4" color: "cfe8d4"
- name: "☁️ Custom" description: ""
- name: ":cloud: Cyberghost"
color: "cfe8d4" color: "cfe8d4"
- name: "☁️ Cyberghost" description: ""
- name: ":cloud: HideMyAss"
color: "cfe8d4" color: "cfe8d4"
- name: "☁️ Giganews" description: ""
- name: ":cloud: IPVanish"
color: "cfe8d4" color: "cfe8d4"
- name: "☁️ HideMyAss" description: ""
- name: ":cloud: IVPN"
color: "cfe8d4" color: "cfe8d4"
- name: "☁️ IPVanish" description: ""
- name: ":cloud: ExpressVPN"
color: "cfe8d4" color: "cfe8d4"
- name: "☁️ IVPN" description: ""
- name: ":cloud: FastestVPN"
color: "cfe8d4" color: "cfe8d4"
- name: "☁️ ExpressVPN" description: ""
- name: ":cloud: Mullvad"
color: "cfe8d4" color: "cfe8d4"
- name: "☁️ FastestVPN" description: ""
- name: ":cloud: NordVPN"
color: "cfe8d4" color: "cfe8d4"
- name: "☁️ Mullvad" description: ""
- name: ":cloud: Perfect Privacy"
color: "cfe8d4" color: "cfe8d4"
- name: "☁️ NordVPN" description: ""
- name: ":cloud: PIA"
color: "cfe8d4" color: "cfe8d4"
- name: "☁️ Perfect Privacy" description: ""
- name: ":cloud: Privado"
color: "cfe8d4" color: "cfe8d4"
- name: "☁️ PIA" description: ""
- name: ":cloud: PrivateVPN"
color: "cfe8d4" color: "cfe8d4"
- name: "☁️ Privado" description: ""
- name: ":cloud: ProtonVPN"
color: "cfe8d4" color: "cfe8d4"
- name: "☁️ PrivateVPN" - name: ":cloud: PureVPN"
color: "cfe8d4" color: "cfe8d4"
- name: "☁️ ProtonVPN" description: ""
- name: ":cloud: SlickVPN"
color: "cfe8d4" color: "cfe8d4"
- name: "☁️ PureVPN" description: ""
- name: ":cloud: Surfshark"
color: "cfe8d4" color: "cfe8d4"
- name: "☁️ SlickVPN" description: ""
- name: ":cloud: Torguard"
color: "cfe8d4" color: "cfe8d4"
- name: "☁️ Surfshark" description: ""
- name: ":cloud: VPNSecure.me"
color: "cfe8d4" color: "cfe8d4"
- name: "☁️ Torguard" - name: ":cloud: VPNUnlimited"
color: "cfe8d4" color: "cfe8d4"
- name: "☁️ VPNSecure.me" description: ""
- name: ":cloud: Vyprvpn"
color: "cfe8d4" color: "cfe8d4"
- name: "☁️ VPNUnlimited" description: ""
- name: ":cloud: WeVPN"
color: "cfe8d4" color: "cfe8d4"
- name: "☁️ Vyprvpn" description: ""
color: "cfe8d4" - name: ":cloud: Windscribe"
- name: "☁️ WeVPN"
color: "cfe8d4"
- name: "☁️ Windscribe"
color: "cfe8d4" color: "cfe8d4"
description: ""
- name: "Category: User error 🤦" # Problem category
from_name: "Category: Config problem 📝" - name: "Config problem"
color: "ffc7ea" color: "ffc7ea"
- name: "Category: Healthcheck 🩺" description: ""
- name: "Openvpn"
color: "ffc7ea" color: "ffc7ea"
- name: "Category: Documentation ✒️" description: ""
description: "A problem with the readme or a code comment." - name: "Wireguard"
color: "ffc7ea" color: "ffc7ea"
- name: "Category: Maintenance ⛓️" description: ""
description: "Anything related to code or other maintenance" - name: "Unbound (DNS over TLS)"
color: "ffc7ea" color: "ffc7ea"
- name: "Category: Logs 📚" description: ""
description: "Something to change in logs" - name: "Firewall"
color: "ffc7ea" color: "ffc7ea"
- name: "Category: Good idea 🎯" description: ""
description: "This is a good idea, judged by the maintainers" - name: "Routing"
color: "ffc7ea" color: "ffc7ea"
- name: "Category: Motivated! 🙌" description: ""
description: "Your pumpness makes me pumped! The issue or PR shows great motivation!" - name: "IPv6"
color: "ffc7ea" color: "ffc7ea"
- name: "Category: Foolproof settings 👼" description: ""
- name: "Port forwarding"
color: "ffc7ea" color: "ffc7ea"
- name: "Category: Label missing ❗" description: ""
- name: "HTTP proxy"
color: "ffc7ea" color: "ffc7ea"
- name: "Category: updater ♻️" description: ""
- name: "Shadowsocks"
color: "ffc7ea" color: "ffc7ea"
description: "Concerns the code to update servers data" description: ""
- name: "Category: New provider 🆕" - name: "Healthcheck server"
color: "ffc7ea" color: "ffc7ea"
- name: "Category: OpenVPN 🔐" description: ""
color: "ffc7ea" - name: "Control server"
- name: "Category: Wireguard 🔐"
color: "ffc7ea"
- name: "Category: DNS 📠"
color: "ffc7ea"
- name: "Category: Firewall ⛓️"
color: "ffc7ea"
- name: "Category: Routing 🛤️"
color: "ffc7ea"
- name: "Category: IPv6 🛰️"
color: "ffc7ea"
- name: "Category: VPN port forwarding 📥"
color: "ffc7ea"
- name: "Category: HTTP proxy 🔁"
color: "ffc7ea"
- name: "Category: Shadowsocks 🔁"
color: "ffc7ea"
- name: "Category: control server ⚙️"
color: "ffc7ea"
- name: "Category: kernel 🧠"
color: "ffc7ea"
- name: "Category: public IP service 💬"
color: "ffc7ea"
- name: "Category: servers storage 📦"
color: "ffc7ea"
- name: "Category: Performance 🚀"
color: "ffc7ea"
- name: "Category: Investigation 🔍"
color: "ffc7ea" color: "ffc7ea"
description: ""

View File

@@ -74,9 +74,6 @@ jobs:
security-events: write security-events: write
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: "^1.23"
- uses: github/codeql-action/init@v3 - uses: github/codeql-action/init@v3
with: with:
languages: go languages: go
@@ -138,7 +135,7 @@ jobs:
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@v6 uses: docker/build-push-action@v5
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 }}

View File

@@ -1,13 +0,0 @@
{
"ignorePatterns": [
{
"pattern": "^https://console.substack.com/p/console-72$"
}
],
"timeout": "20s",
"retryOn429": false,
"fallbackRetryDelay": "30s",
"aliveStatusCodes": [
200
]
}

View File

@@ -20,7 +20,7 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: DavidAnson/markdownlint-cli2-action@v16 - uses: DavidAnson/markdownlint-cli2-action@v14
with: with:
globs: "**.md" globs: "**.md"
config: .markdownlint.json config: .markdownlint.json
@@ -35,9 +35,8 @@ jobs:
- uses: gaurav-nelson/github-action-markdown-link-check@v1 - uses: gaurav-nelson/github-action-markdown-link-check@v1
with: with:
use-quiet-mode: yes use-quiet-mode: yes
config-file: .github/workflows/configs/mlc-config.json
- uses: peter-evans/dockerhub-description@v4 - uses: peter-evans/dockerhub-description@v3
if: github.repository == 'qdm12/gluetun' && github.event_name == 'push' if: github.repository == 'qdm12/gluetun' && github.event_name == 'push'
with: with:
username: qmcgaw username: qmcgaw

View File

@@ -7,12 +7,27 @@ issues:
- path: _test\.go - path: _test\.go
linters: linters:
- dupl - dupl
- err113 - goerr113
- containedctx - containedctx
- goconst
- maintidx - maintidx
- path: "internal\\/server\\/.+\\.go" - path: "internal\\/server\\/.+\\.go"
linters: linters:
- dupl - dupl
- path: "internal\\/configuration\\/settings\\/.+\\.go"
linters:
- dupl
- text: "^mnd: Magic number: 0[0-9]{3}, in <argument> detected$"
source: "^.+= os\\.OpenFile\\(.+, .+, 0[0-9]{3}\\)"
linters:
- gomnd
- text: "^mnd: Magic number: 0[0-9]{3}, in <argument> detected$"
source: "^.+= os\\.MkdirAll\\(.+, 0[0-9]{3}\\)"
linters:
- gomnd
- linters:
- lll
source: "^//go:generate .+$"
- text: "returns interface \\(github\\.com\\/vishvananda\\/netlink\\.Link\\)" - text: "returns interface \\(github\\.com\\/vishvananda\\/netlink\\.Link\\)"
linters: linters:
- ireturn - ireturn
@@ -20,6 +35,18 @@ issues:
text: "newCipherDESCBCBlock returns interface \\(github\\.com\\/youmark\\/pkcs8\\.Cipher\\)" text: "newCipherDESCBCBlock returns interface \\(github\\.com\\/youmark\\/pkcs8\\.Cipher\\)"
linters: linters:
- ireturn - ireturn
- 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:
@@ -29,19 +56,17 @@ linters:
- asciicheck - asciicheck
- bidichk - bidichk
- bodyclose - bodyclose
- canonicalheader
- containedctx - containedctx
- copyloopvar
- decorder - decorder
- dogsled - dogsled
- dupl - dupl
- dupword - dupword
- durationcheck - durationcheck
- err113
- errchkjson - errchkjson
- errname - errname
- execinquery
- exhaustive - exhaustive
- fatcontext - exportloopref
- forcetypeassert - forcetypeassert
- gci - gci
- gocheckcompilerdirectives - gocheckcompilerdirectives
@@ -52,9 +77,10 @@ linters:
- gocritic - gocritic
- gocyclo - gocyclo
- godot - godot
- gofumpt - goerr113
- goheader - goheader
- goimports - goimports
- gomnd
- gomoddirectives - gomoddirectives
- goprintffuncname - goprintffuncname
- gosec - gosec
@@ -62,14 +88,12 @@ linters:
- grouper - grouper
- importas - importas
- interfacebloat - interfacebloat
- intrange
- ireturn - ireturn
- lll - lll
- maintidx - maintidx
- makezero - makezero
- mirror - mirror
- misspell - misspell
- mnd
- musttag - musttag
- nakedret - nakedret
- nestif - nestif
@@ -96,3 +120,9 @@ linters:
- wastedassign - wastedassign
- whitespace - whitespace
- zerologlint - zerologlint
run:
skip-dirs:
- .devcontainer
- .github
- doc

View File

@@ -21,7 +21,7 @@
"go.useLanguageServer": true, "go.useLanguageServer": true,
"[go]": { "[go]": {
"editor.codeActionsOnSave": { "editor.codeActionsOnSave": {
"source.organizeImports": "explicit" "source.organizeImports": true
} }
}, },
"go.lintTool": "golangci-lint", "go.lintTool": "golangci-lint",

View File

@@ -1,8 +1,8 @@
ARG ALPINE_VERSION=3.20 ARG ALPINE_VERSION=3.18
ARG GO_ALPINE_VERSION=3.20 ARG GO_ALPINE_VERSION=3.18
ARG GO_VERSION=1.23 ARG GO_VERSION=1.21
ARG XCPUTRANSLATE_VERSION=v0.6.0 ARG XCPUTRANSLATE_VERSION=v0.6.0
ARG GOLANGCI_LINT_VERSION=v1.61.0 ARG GOLANGCI_LINT_VERSION=v1.56.2
ARG MOCKGEN_VERSION=v1.6.0 ARG MOCKGEN_VERSION=v1.6.0
ARG BUILDPLATFORM=linux/amd64 ARG BUILDPLATFORM=linux/amd64
@@ -76,26 +76,23 @@ LABEL \
ENV VPN_SERVICE_PROVIDER=pia \ ENV VPN_SERVICE_PROVIDER=pia \
VPN_TYPE=openvpn \ VPN_TYPE=openvpn \
# Common VPN options # Common VPN options
VPN_ENDPOINT_IP= \
VPN_ENDPOINT_PORT= \
VPN_INTERFACE=tun0 \ VPN_INTERFACE=tun0 \
# OpenVPN # OpenVPN
OPENVPN_ENDPOINT_IP= \
OPENVPN_ENDPOINT_PORT= \
OPENVPN_PROTOCOL=udp \ OPENVPN_PROTOCOL=udp \
OPENVPN_USER= \ OPENVPN_USER= \
OPENVPN_PASSWORD= \ OPENVPN_PASSWORD= \
OPENVPN_USER_SECRETFILE=/run/secrets/openvpn_user \ OPENVPN_USER_SECRETFILE=/run/secrets/openvpn_user \
OPENVPN_PASSWORD_SECRETFILE=/run/secrets/openvpn_password \ OPENVPN_PASSWORD_SECRETFILE=/run/secrets/openvpn_password \
OPENVPN_VERSION=2.6 \ OPENVPN_VERSION=2.5 \
OPENVPN_VERBOSITY=1 \ OPENVPN_VERBOSITY=1 \
OPENVPN_FLAGS= \ OPENVPN_FLAGS= \
OPENVPN_CIPHERS= \ OPENVPN_CIPHERS= \
OPENVPN_AUTH= \ OPENVPN_AUTH= \
OPENVPN_PROCESS_USER=root \ OPENVPN_PROCESS_USER=root \
OPENVPN_MSSFIX= \
OPENVPN_CUSTOM_CONFIG= \ OPENVPN_CUSTOM_CONFIG= \
# Wireguard # Wireguard
WIREGUARD_ENDPOINT_IP= \
WIREGUARD_ENDPOINT_PORT= \
WIREGUARD_CONF_SECRETFILE=/run/secrets/wg0.conf \ WIREGUARD_CONF_SECRETFILE=/run/secrets/wg0.conf \
WIREGUARD_PRIVATE_KEY= \ WIREGUARD_PRIVATE_KEY= \
WIREGUARD_PRIVATE_KEY_SECRETFILE=/run/secrets/wireguard_private_key \ WIREGUARD_PRIVATE_KEY_SECRETFILE=/run/secrets/wireguard_private_key \
@@ -103,7 +100,6 @@ ENV VPN_SERVICE_PROVIDER=pia \
WIREGUARD_PRESHARED_KEY_SECRETFILE=/run/secrets/wireguard_preshared_key \ WIREGUARD_PRESHARED_KEY_SECRETFILE=/run/secrets/wireguard_preshared_key \
WIREGUARD_PUBLIC_KEY= \ WIREGUARD_PUBLIC_KEY= \
WIREGUARD_ALLOWED_IPS= \ WIREGUARD_ALLOWED_IPS= \
WIREGUARD_PERSISTENT_KEEPALIVE_INTERVAL=0 \
WIREGUARD_ADDRESSES= \ WIREGUARD_ADDRESSES= \
WIREGUARD_ADDRESSES_SECRETFILE=/run/secrets/wireguard_addresses \ WIREGUARD_ADDRESSES_SECRETFILE=/run/secrets/wireguard_addresses \
WIREGUARD_MTU=1400 \ WIREGUARD_MTU=1400 \
@@ -123,8 +119,6 @@ ENV VPN_SERVICE_PROVIDER=pia \
VPN_PORT_FORWARDING_LISTENING_PORT=0 \ VPN_PORT_FORWARDING_LISTENING_PORT=0 \
VPN_PORT_FORWARDING_PROVIDER= \ VPN_PORT_FORWARDING_PROVIDER= \
VPN_PORT_FORWARDING_STATUS_FILE="/tmp/gluetun/forwarded_port" \ VPN_PORT_FORWARDING_STATUS_FILE="/tmp/gluetun/forwarded_port" \
VPN_PORT_FORWARDING_USERNAME= \
VPN_PORT_FORWARDING_PASSWORD= \
# # Cyberghost only: # # Cyberghost only:
OPENVPN_CERT= \ OPENVPN_CERT= \
OPENVPN_KEY= \ OPENVPN_KEY= \
@@ -139,20 +133,16 @@ ENV VPN_SERVICE_PROVIDER=pia \
SERVER_NUMBER= \ SERVER_NUMBER= \
# # PIA only: # # PIA only:
SERVER_NAMES= \ SERVER_NAMES= \
# # VPNUnlimited and ProtonVPN only:
STREAM_ONLY= \
FREE_ONLY= \
# # ProtonVPN only: # # ProtonVPN only:
SECURE_CORE_ONLY= \ FREE_ONLY= \
TOR_ONLY= \
# # Surfshark only: # # Surfshark only:
MULTIHOP_ONLY= \ MULTIHOP_ONLY= \
# # VPN Secure only: # # VPN Secure only:
PREMIUM_ONLY= \ PREMIUM_ONLY= \
# # PIA and ProtonVPN only: # # PIA only:
PORT_FORWARD_ONLY= \ PORT_FORWARD_ONLY= \
# Firewall # Firewall
FIREWALL_ENABLED_DISABLING_IT_SHOOTS_YOU_IN_YOUR_FOOT=on \ FIREWALL=on \
FIREWALL_VPN_INPUT_PORTS= \ FIREWALL_VPN_INPUT_PORTS= \
FIREWALL_INPUT_PORTS= \ FIREWALL_INPUT_PORTS= \
FIREWALL_OUTBOUND_SUBNETS= \ FIREWALL_OUTBOUND_SUBNETS= \
@@ -169,6 +159,9 @@ ENV VPN_SERVICE_PROVIDER=pia \
DOT=on \ DOT=on \
DOT_PROVIDERS=cloudflare \ DOT_PROVIDERS=cloudflare \
DOT_PRIVATE_ADDRESS=127.0.0.1/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,169.254.0.0/16,::1/128,fc00::/7,fe80::/10,::ffff:7f00:1/104,::ffff:a00:0/104,::ffff:a9fe:0/112,::ffff:ac10:0/108,::ffff:c0a8:0/112 \ DOT_PRIVATE_ADDRESS=127.0.0.1/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,169.254.0.0/16,::1/128,fc00::/7,fe80::/10,::ffff:7f00:1/104,::ffff:a00:0/104,::ffff:a9fe:0/112,::ffff:ac10:0/108,::ffff:c0a8:0/112 \
DOT_VERBOSITY=1 \
DOT_VERBOSITY_DETAILS=0 \
DOT_VALIDATION_LOGLEVEL=0 \
DOT_CACHING=on \ DOT_CACHING=on \
DOT_IPV6=off \ DOT_IPV6=off \
BLOCK_MALICIOUS=on \ BLOCK_MALICIOUS=on \
@@ -197,18 +190,15 @@ ENV VPN_SERVICE_PROVIDER=pia \
# Control server # Control server
HTTP_CONTROL_SERVER_LOG=on \ HTTP_CONTROL_SERVER_LOG=on \
HTTP_CONTROL_SERVER_ADDRESS=":8000" \ HTTP_CONTROL_SERVER_ADDRESS=":8000" \
HTTP_CONTROL_SERVER_AUTH_CONFIG_FILEPATH=/gluetun/auth/config.toml \
# Server data updater # Server data updater
UPDATER_PERIOD=0 \ UPDATER_PERIOD=0 \
UPDATER_MIN_RATIO=0.8 \ UPDATER_MIN_RATIO=0.8 \
UPDATER_VPN_SERVICE_PROVIDERS= \ UPDATER_VPN_SERVICE_PROVIDERS= \
# Public IP # Public IP
PUBLICIP_FILE="/tmp/gluetun/ip" \ PUBLICIP_FILE="/tmp/gluetun/ip" \
PUBLICIP_ENABLED=on \ PUBLICIP_PERIOD=12h \
PUBLICIP_API=ipinfo,ifconfigco,ip2location,cloudflare \ PUBLICIP_API=ipinfo \
PUBLICIP_API_TOKEN= \ PUBLICIP_API_TOKEN= \
# Storage
STORAGE_FILEPATH=/gluetun/servers.json \
# Pprof # Pprof
PPROF_ENABLED=no \ PPROF_ENABLED=no \
PPROF_BLOCK_PROFILE_RATE=0 \ PPROF_BLOCK_PROFILE_RATE=0 \
@@ -221,15 +211,18 @@ ENV VPN_SERVICE_PROVIDER=pia \
PGID= PGID=
ENTRYPOINT ["/gluetun-entrypoint"] 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=3 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 wget && \
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.17/main" openvpn\~2.5 && \
mv /usr/sbin/openvpn /usr/sbin/openvpn2.5 && \ mv /usr/sbin/openvpn /usr/sbin/openvpn2.5 && \
apk del openvpn && \ apk del openvpn && \
apk add --no-cache --update openvpn ca-certificates iptables iptables-legacy 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.6 && \
rm -rf /var/cache/apk/* /etc/openvpn/*.sh /usr/lib/openvpn/plugins/openvpn-plugin-down-root.so && \ # Fix vulnerability issue
apk add --no-cache --update busybox && \
rm -rf /var/cache/apk/* /etc/unbound/* /usr/sbin/unbound-* /etc/openvpn/*.sh /usr/lib/openvpn/plugins/openvpn-plugin-down-root.so && \
deluser openvpn && \ deluser openvpn && \
deluser unbound && \
mkdir /gluetun mkdir /gluetun
COPY --from=build /tmp/gobuild/entrypoint /gluetun-entrypoint COPY --from=build /tmp/gobuild/entrypoint /gluetun-entrypoint

View File

@@ -56,25 +56,26 @@ Lightweight swiss-knife-like VPN client to multiple VPN service providers
## Features ## Features
- Based on Alpine 3.20 for a small Docker image of 35.6MB - Based on Alpine 3.18 for a small Docker image of 35.6MB
- Supports: **AirVPN**, **Cyberghost**, **ExpressVPN**, **FastestVPN**, **Giganews**, **HideMyAss**, **IPVanish**, **IVPN**, **Mullvad**, **NordVPN**, **Perfect Privacy**, **Privado**, **Private Internet Access**, **PrivateVPN**, **ProtonVPN**, **PureVPN**, **SlickVPN**, **Surfshark**, **TorGuard**, **VPNSecure.me**, **VPNUnlimited**, **Vyprvpn**, **WeVPN**, **Windscribe** servers - Supports: **AirVPN**, **Cyberghost**, **ExpressVPN**, **FastestVPN**, **HideMyAss**, **IPVanish**, **IVPN**, **Mullvad**, **NordVPN**, **Perfect Privacy**, **Privado**, **Private Internet Access**, **PrivateVPN**, **ProtonVPN**, **PureVPN**, **SlickVPN**, **Surfshark**, **TorGuard**, **VPNSecure.me**, **VPNUnlimited**, **Vyprvpn**, **WeVPN**, **Windscribe** servers
- Supports OpenVPN for all providers listed - Supports OpenVPN for all providers listed
- Supports Wireguard both kernelspace and userspace - Supports Wireguard both kernelspace and userspace
- For **AirVPN**, **FastestVPN**, **Ivpn**, **Mullvad**, **NordVPN**, **Perfect privacy**, **ProtonVPN**, **Surfshark** and **Windscribe** - For **AirVPN**, **Ivpn**, **Mullvad**, **NordVPN**, **Surfshark** and **Windscribe**
- For **Cyberghost**, **Private Internet Access**, **PrivateVPN**, **PureVPN**, **Torguard**, **VPN Unlimited**, **VyprVPN** and **WeVPN** using [the custom provider](https://github.com/qdm12/gluetun-wiki/blob/main/setup/providers/custom.md) - For **ProtonVPN**, **PureVPN**, **Torguard**, **VPN Unlimited** and **WeVPN** using [the custom provider](https://github.com/qdm12/gluetun-wiki/blob/main/setup/providers/custom.md)
- For custom Wireguard configurations using [the custom provider](https://github.com/qdm12/gluetun-wiki/blob/main/setup/providers/custom.md) - For custom Wireguard configurations using [the custom provider](https://github.com/qdm12/gluetun-wiki/blob/main/setup/providers/custom.md)
- More in progress, see [#134](https://github.com/qdm12/gluetun/issues/134) - More in progress, see [#134](https://github.com/qdm12/gluetun/issues/134)
- DNS over TLS baked in with service provider(s) of your choice - DNS over TLS baked in with service provider(s) of your choice
- DNS fine blocking of malicious/ads/surveillance hostnames and IP addresses, with live update every 24 hours - DNS fine blocking of malicious/ads/surveillance hostnames and IP addresses, with live update every 24 hours
- Choose the vpn network protocol, `udp` or `tcp` - Choose the vpn network protocol, `udp` or `tcp`
- Built in firewall kill switch to allow traffic only with needed the VPN servers and LAN devices - Built in firewall kill switch to allow traffic only with needed the VPN servers and LAN devices
- Built in Shadowsocks proxy server (protocol based on SOCKS5 with an encryption layer, tunnels TCP+UDP) - Built in Shadowsocks proxy (protocol based on SOCKS5 with an encryption layer, tunnels TCP+UDP)
- Built in HTTP proxy (tunnels HTTP and HTTPS through TCP) - Built in HTTP proxy (tunnels HTTP and HTTPS through TCP)
- [Connect other containers to it](https://github.com/qdm12/gluetun-wiki/blob/main/setup/connect-a-container-to-gluetun.md) - [Connect other containers to it](https://github.com/qdm12/gluetun-wiki/blob/main/setup/connect-a-container-to-gluetun.md)
- [Connect LAN devices to it](https://github.com/qdm12/gluetun-wiki/blob/main/setup/connect-a-lan-device-to-gluetun.md) - [Connect LAN devices to it](https://github.com/qdm12/gluetun-wiki/blob/main/setup/connect-a-lan-device-to-gluetun.md)
- Compatible with amd64, i686 (32 bit), **ARM** 64 bit, ARM 32 bit v6 and v7, and even ppc64le 🎆 - Compatible with amd64, i686 (32 bit), **ARM** 64 bit, ARM 32 bit v6 and v7, and even ppc64le 🎆
- Custom VPN server side port forwarding for [Perfect Privacy](https://github.com/qdm12/gluetun-wiki/blob/main/setup/providers/perfect-privacy.md#vpn-server-port-forwarding), [Private Internet Access](https://github.com/qdm12/gluetun-wiki/blob/main/setup/providers/private-internet-access.md#vpn-server-port-forwarding), [PrivateVPN](https://github.com/qdm12/gluetun-wiki/blob/main/setup/providers/privatevpn.md#vpn-server-port-forwarding) and [ProtonVPN](https://github.com/qdm12/gluetun-wiki/blob/main/setup/providers/protonvpn.md#vpn-server-port-forwarding) - [Custom VPN server side port forwarding for Private Internet Access](https://github.com/qdm12/gluetun-wiki/blob/main/setup/providers/private-internet-access.md#vpn-server-port-forwarding)
- Possibility of split horizon DNS by selecting multiple DNS over TLS providers - Possibility of split horizon DNS by selecting multiple DNS over TLS providers
- Unbound subprogram drops root privileges once launched
- Can work as a Kubernetes sidecar container, thanks @rorph - Can work as a Kubernetes sidecar container, thanks @rorph
## Setup ## Setup
@@ -83,7 +84,7 @@ Lightweight swiss-knife-like VPN client to multiple VPN service providers
Go to the [Wiki](https://github.com/qdm12/gluetun-wiki)! Go to the [Wiki](https://github.com/qdm12/gluetun-wiki)!
[🐛 Found a bug in the Wiki?!](https://github.com/qdm12/gluetun-wiki/issues/new/choose) [🐛 Found a bug in the Wiki?!](https://github.com/qdm12/gluetun-wiki/issues/new)
Here's a docker-compose.yml for the laziest: Here's a docker-compose.yml for the laziest:

View File

@@ -4,10 +4,8 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"io/fs"
"net/http" "net/http"
"os" "os"
"os/exec"
"os/signal" "os/signal"
"strings" "strings"
"syscall" "syscall"
@@ -15,11 +13,13 @@ import (
_ "time/tzdata" _ "time/tzdata"
_ "github.com/breml/rootcerts" _ "github.com/breml/rootcerts"
"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/command"
"github.com/qdm12/gluetun/internal/configuration/settings" "github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/configuration/sources/env"
"github.com/qdm12/gluetun/internal/configuration/sources/files" "github.com/qdm12/gluetun/internal/configuration/sources/files"
mux "github.com/qdm12/gluetun/internal/configuration/sources/merge"
"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"
@@ -34,6 +34,7 @@ import (
"github.com/qdm12/gluetun/internal/pprof" "github.com/qdm12/gluetun/internal/pprof"
"github.com/qdm12/gluetun/internal/provider" "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"
@@ -43,14 +44,14 @@ import (
"github.com/qdm12/gluetun/internal/updater/resolver" "github.com/qdm12/gluetun/internal/updater/resolver"
"github.com/qdm12/gluetun/internal/updater/unzip" "github.com/qdm12/gluetun/internal/updater/unzip"
"github.com/qdm12/gluetun/internal/vpn" "github.com/qdm12/gluetun/internal/vpn"
"github.com/qdm12/gosettings/reader" "github.com/qdm12/golibs/command"
"github.com/qdm12/gosettings/reader/sources/env"
"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/log"
"github.com/qdm12/updated/pkg/dnscrypto"
) )
//nolint:gochecknoglobals //nolint:gochecknoglobals
@@ -79,23 +80,16 @@ func main() {
netLinkDebugLogger := logger.New(log.SetComponent("netlink")) netLinkDebugLogger := logger.New(log.SetComponent("netlink"))
netLinker := netlink.New(netLinkDebugLogger) netLinker := netlink.New(netLinkDebugLogger)
cli := cli.New() cli := cli.New()
cmder := command.New() cmder := command.NewCmder()
reader := reader.New(reader.Settings{ secretsReader := secrets.New()
Sources: []reader.Source{ filesReader := files.New()
secrets.New(logger), envReader := env.New(logger)
files.New(logger), muxReader := mux.New(secretsReader, filesReader, envReader)
env.New(env.Settings{}),
},
HandleDeprecatedKey: func(source, deprecatedKey, currentKey string) {
logger.Warn("You are using the old " + source + " " + deprecatedKey +
", please consider changing it to " + currentKey)
},
})
errorCh := make(chan error) errorCh := make(chan error)
go func() { go func() {
errorCh <- _main(ctx, buildInfo, args, logger, reader, tun, netLinker, cmder, cli) errorCh <- _main(ctx, buildInfo, args, logger, muxReader, tun, netLinker, cmder, cli)
}() }()
var err error var err error
@@ -139,34 +133,33 @@ func main() {
} }
} }
var errCommandUnknown = errors.New("command is unknown") var (
errCommandUnknown = errors.New("command is unknown")
)
//nolint:gocognit,gocyclo,maintidx //nolint:gocognit,gocyclo,maintidx
func _main(ctx context.Context, buildInfo models.BuildInformation, func _main(ctx context.Context, buildInfo models.BuildInformation,
args []string, logger log.LoggerInterface, reader *reader.Reader, args []string, logger log.LoggerInterface, source Source,
tun Tun, netLinker netLinker, cmder RunStarter, tun Tun, netLinker netLinker, cmder command.RunStarter,
cli clier, cli clier) error {
) error {
if len(args) > 1 { // cli operation if len(args) > 1 { // cli operation
switch args[1] { switch args[1] {
case "healthcheck": case "healthcheck":
return cli.HealthCheck(ctx, reader, logger) return cli.HealthCheck(ctx, source, logger)
case "clientkey": case "clientkey":
return cli.ClientKey(args[2:]) return cli.ClientKey(args[2:])
case "openvpnconfig": case "openvpnconfig":
return cli.OpenvpnConfig(logger, reader, netLinker) return cli.OpenvpnConfig(logger, source, netLinker)
case "update": case "update":
return cli.Update(ctx, args[2:], logger) return cli.Update(ctx, args[2:], logger)
case "format-servers": case "format-servers":
return cli.FormatServers(args[2:]) return cli.FormatServers(args[2:])
case "genkey":
return cli.GenKey(args[2:])
default: default:
return fmt.Errorf("%w: %s", errCommandUnknown, args[1]) return fmt.Errorf("%w: %s", errCommandUnknown, args[1])
} }
} }
announcementExp, err := time.Parse(time.RFC3339, "2024-12-01T00:00:00Z") announcementExp, err := time.Parse(time.RFC3339, "2023-07-01T00:00:00Z")
if err != nil { if err != nil {
return err return err
} }
@@ -176,8 +169,8 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
Emails: []string{"quentin.mcgaw@gmail.com"}, Emails: []string{"quentin.mcgaw@gmail.com"},
Version: buildInfo.Version, Version: buildInfo.Version,
Commit: buildInfo.Commit, Commit: buildInfo.Commit,
Created: buildInfo.Created, BuildDate: buildInfo.Created,
Announcement: "All control server routes will become private by default after the v3.41.0 release", Announcement: "Wiki moved to https://github.com/qdm12/gluetun-wiki",
AnnounceExp: announcementExp, AnnounceExp: announcementExp,
// Sponsor information // Sponsor information
PaypalUser: "qmcgaw", PaypalUser: "qmcgaw",
@@ -187,22 +180,17 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
fmt.Println(line) fmt.Println(line)
} }
var allSettings settings.Settings allSettings, err := source.Read()
err = allSettings.Read(reader, logger)
if err != nil { if err != nil {
return err return err
} }
allSettings.SetDefaults()
// Note: no need to validate minimal settings for the firewall: // Note: no need to validate minimal settings for the firewall:
// - global log level is parsed below // - global log level is parsed from source
// - firewall Debug and Enabled are booleans parsed from source // - firewall Debug and Enabled are booleans parsed from source
logLevel, err := log.ParseLevel(allSettings.Log.Level)
if err != nil { logger.Patch(log.SetLevel(*allSettings.Log.Level))
return fmt.Errorf("log level: %w", err) netLinker.PatchLoggerLevel(*allSettings.Log.Level)
}
logger.Patch(log.SetLevel(logLevel))
netLinker.PatchLoggerLevel(logLevel)
routingLogger := logger.New(log.SetComponent("routing")) routingLogger := logger.New(log.SetComponent("routing"))
if *allSettings.Firewall.Debug { // To remove in v4 if *allSettings.Firewall.Debug { // To remove in v4
@@ -239,7 +227,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
// 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.New(log.SetComponent("storage"))
storage, err := storage.New(storageLogger, *allSettings.Storage.Filepath) storage, err := storage.New(storageLogger, constants.ServersData)
if err != nil { if err != nil {
return err return err
} }
@@ -249,7 +237,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
return fmt.Errorf("checking for IPv6 support: %w", err) return fmt.Errorf("checking for IPv6 support: %w", err)
} }
err = allSettings.Validate(storage, ipv6Supported, logger) err = allSettings.Validate(storage, ipv6Supported)
if err != nil { if err != nil {
return err return err
} }
@@ -269,12 +257,19 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
ovpnConf := openvpn.New( ovpnConf := openvpn.New(
logger.New(log.SetComponent("openvpn configurator")), logger.New(log.SetComponent("openvpn configurator")),
cmder, puid, pgid) cmder, puid, pgid)
dnsCrypto := dnscrypto.New(httpClient, "", "")
const cacertsPath = "/etc/ssl/certs/ca-certificates.crt"
dnsConf := unbound.NewConfigurator(nil, cmder, dnsCrypto,
"/etc/unbound", "/usr/sbin/unbound", cacertsPath)
err = printVersions(ctx, logger, []printVersionElement{ err = printVersions(ctx, logger, []printVersionElement{
{name: "Alpine", getVersion: alpineConf.Version}, {name: "Alpine", getVersion: alpineConf.Version},
{name: "OpenVPN 2.5", getVersion: ovpnConf.Version25}, {name: "OpenVPN 2.5", getVersion: ovpnConf.Version25},
{name: "OpenVPN 2.6", getVersion: ovpnConf.Version26}, {name: "OpenVPN 2.6", getVersion: ovpnConf.Version26},
{name: "IPtables", getVersion: firewallConf.Version}, {name: "Unbound", getVersion: dnsConf.Version},
{name: "IPtables", getVersion: func(ctx context.Context) (version string, err error) {
return firewall.Version(ctx, cmder)
}},
}) })
if err != nil { if err != nil {
return err return err
@@ -286,13 +281,10 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
logger.Warn(warning) logger.Warn(warning)
} }
const permission = fs.FileMode(0o644) if err := os.MkdirAll("/tmp/gluetun", 0644); err != nil {
err = os.MkdirAll("/tmp/gluetun", permission)
if err != nil {
return err return err
} }
err = os.MkdirAll("/gluetun", permission) if err := os.MkdirAll("/gluetun", 0644); err != nil {
if err != nil {
return err return err
} }
@@ -304,8 +296,15 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
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))
} }
// set it for Unbound
// TODO remove this when migrating to qdm12/dns v2
allSettings.DNS.DoT.Unbound.Username = nonRootUsername
allSettings.VPN.OpenVPN.ProcessUser = nonRootUsername allSettings.VPN.OpenVPN.ProcessUser = nonRootUsername
if err := os.Chown("/etc/unbound", puid, pgid); err != nil {
return err
}
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?")
@@ -364,8 +363,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
} }
defaultGroupOptions := []group.Option{ defaultGroupOptions := []group.Option{
group.OptionTimeout(defaultShutdownTimeout), group.OptionTimeout(defaultShutdownTimeout),
group.OptionOnSuccess(defaultShutdownOnSuccess), group.OptionOnSuccess(defaultShutdownOnSuccess)}
}
controlGroupHandler := goshutdown.NewGroupHandler("control", defaultGroupOptions...) controlGroupHandler := goshutdown.NewGroupHandler("control", defaultGroupOptions...)
tickersGroupHandler := goshutdown.NewGroupHandler("tickers", defaultGroupOptions...) tickersGroupHandler := goshutdown.NewGroupHandler("tickers", defaultGroupOptions...)
@@ -388,29 +386,28 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
return fmt.Errorf("starting port forwarding loop: %w", err) return fmt.Errorf("starting port forwarding loop: %w", err)
} }
dnsLogger := logger.New(log.SetComponent("dns")) unboundLogger := logger.New(log.SetComponent("dns"))
dnsLooper, err := dns.NewLoop(allSettings.DNS, httpClient, unboundLooper := dns.NewLoop(dnsConf, allSettings.DNS, httpClient,
dnsLogger) unboundLogger)
if err != nil {
return fmt.Errorf("creating DNS loop: %w", err)
}
dnsHandler, dnsCtx, dnsDone := goshutdown.NewGoRoutineHandler( dnsHandler, dnsCtx, dnsDone := goshutdown.NewGoRoutineHandler(
"dns", goroutine.OptionTimeout(defaultShutdownTimeout)) "unbound", goroutine.OptionTimeout(defaultShutdownTimeout))
// wait for dnsLooper.Restart or its ticker launched with RunRestartTicker // wait for unboundLooper.Restart or its ticker launched with RunRestartTicker
go dnsLooper.Run(dnsCtx, dnsDone) go unboundLooper.Run(dnsCtx, dnsDone)
otherGroupHandler.Add(dnsHandler) otherGroupHandler.Add(dnsHandler)
dnsTickerHandler, dnsTickerCtx, dnsTickerDone := goshutdown.NewGoRoutineHandler( dnsTickerHandler, dnsTickerCtx, dnsTickerDone := goshutdown.NewGoRoutineHandler(
"dns ticker", goroutine.OptionTimeout(defaultShutdownTimeout)) "dns ticker", goroutine.OptionTimeout(defaultShutdownTimeout))
go dnsLooper.RunRestartTicker(dnsTickerCtx, dnsTickerDone) go unboundLooper.RunRestartTicker(dnsTickerCtx, dnsTickerDone)
controlGroupHandler.Add(dnsTickerHandler) controlGroupHandler.Add(dnsTickerHandler)
publicIPLooper, err := publicip.NewLoop(allSettings.PublicIP, puid, pgid, httpClient, publicipAPI, _ := pubipapi.ParseProvider(allSettings.PublicIP.API)
logger.New(log.SetComponent("ip getter"))) ipFetcher, err := pubipapi.New(publicipAPI, httpClient, *allSettings.PublicIP.APIToken)
if err != nil { if err != nil {
return fmt.Errorf("creating public ip loop: %w", err) return fmt.Errorf("creating public IP API client: %w", err)
} }
publicIPLooper := publicip.NewLoop(ipFetcher,
logger.New(log.SetComponent("ip getter")),
allSettings.PublicIP, puid, pgid)
publicIPRunError, err := publicIPLooper.Start(ctx) publicIPRunError, err := publicIPLooper.Start(ctx)
if err != nil { if err != nil {
return fmt.Errorf("starting public ip loop: %w", err) return fmt.Errorf("starting public ip loop: %w", err)
@@ -422,12 +419,12 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
parallelResolver := resolver.NewParallelResolver(allSettings.Updater.DNSAddress) parallelResolver := resolver.NewParallelResolver(allSettings.Updater.DNSAddress)
openvpnFileExtractor := extract.New() openvpnFileExtractor := extract.New()
providers := provider.NewProviders(storage, time.Now, updaterLogger, providers := provider.NewProviders(storage, time.Now, updaterLogger,
httpClient, unzipper, parallelResolver, publicIPLooper.Fetcher(), openvpnFileExtractor) httpClient, unzipper, parallelResolver, ipFetcher, openvpnFileExtractor)
vpnLogger := logger.New(log.SetComponent("vpn")) vpnLogger := logger.New(log.SetComponent("vpn"))
vpnLooper := vpn.NewLoop(allSettings.VPN, ipv6Supported, allSettings.Firewall.VPNInputPorts, vpnLooper := vpn.NewLoop(allSettings.VPN, ipv6Supported, allSettings.Firewall.VPNInputPorts,
providers, storage, ovpnConf, netLinker, firewallConf, routingConf, portForwardLooper, providers, storage, ovpnConf, netLinker, firewallConf, routingConf, portForwardLooper,
cmder, publicIPLooper, dnsLooper, 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))
@@ -467,8 +464,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
"http server", goroutine.OptionTimeout(defaultShutdownTimeout)) "http server", goroutine.OptionTimeout(defaultShutdownTimeout))
httpServer, err := server.New(httpServerCtx, controlServerAddress, controlServerLogging, httpServer, err := server.New(httpServerCtx, controlServerAddress, controlServerLogging,
logger.New(log.SetComponent("http server")), logger.New(log.SetComponent("http server")),
allSettings.ControlServer.AuthFilePath, buildInfo, vpnLooper, portForwardLooper, unboundLooper, updaterLooper, publicIPLooper,
buildInfo, vpnLooper, portForwardLooper, dnsLooper, updaterLooper, publicIPLooper,
storage, ipv6Supported) storage, ipv6Supported)
if err != nil { if err != nil {
return fmt.Errorf("setting up control server: %w", err) return fmt.Errorf("setting up control server: %w", err)
@@ -528,8 +524,7 @@ type infoer interface {
} }
func printVersions(ctx context.Context, logger infoer, func printVersions(ctx context.Context, logger infoer,
elements []printVersionElement, elements []printVersionElement) (err error) {
) (err error) {
const timeout = 5 * time.Second const timeout = 5 * time.Second
ctx, cancel := context.WithTimeout(ctx, timeout) ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel() defer cancel()
@@ -587,10 +582,9 @@ type Linker interface {
type clier interface { type clier interface {
ClientKey(args []string) error ClientKey(args []string) error
FormatServers(args []string) error FormatServers(args []string) error
OpenvpnConfig(logger cli.OpenvpnConfigLogger, reader *reader.Reader, ipv6Checker cli.IPv6Checker) error OpenvpnConfig(logger cli.OpenvpnConfigLogger, source cli.Source, ipv6Checker cli.IPv6Checker) error
HealthCheck(ctx context.Context, reader *reader.Reader, warner cli.Warner) error HealthCheck(ctx context.Context, source cli.Source, warner cli.Warner) error
Update(ctx context.Context, args []string, logger cli.UpdaterLogger) error Update(ctx context.Context, args []string, logger cli.UpdaterLogger) error
GenKey(args []string) error
} }
type Tun interface { type Tun interface {
@@ -598,8 +592,8 @@ type Tun interface {
Create(tunDevice string) error Create(tunDevice string) error
} }
type RunStarter interface { type Source interface {
Run(cmd *exec.Cmd) (output string, err error) Read() (settings settings.Settings, err error)
Start(cmd *exec.Cmd) (stdoutLines, stderrLines <-chan string, ReadHealth() (health settings.Health, err error)
waitError <-chan error, err error) String() string
} }

73
go.mod
View File

@@ -1,62 +1,55 @@
module github.com/qdm12/gluetun module github.com/qdm12/gluetun
go 1.23 go 1.21
require ( require (
github.com/breml/rootcerts v0.2.18 github.com/breml/rootcerts v0.2.17
github.com/fatih/color v1.18.0 github.com/fatih/color v1.16.0
github.com/golang/mock v1.6.0 github.com/golang/mock v1.6.0
github.com/klauspost/compress v1.17.9 github.com/klauspost/compress v1.17.4
github.com/klauspost/pgzip v1.2.6 github.com/klauspost/pgzip v1.2.6
github.com/pelletier/go-toml/v2 v2.2.2 github.com/qdm12/dns v1.11.0
github.com/qdm12/dns/v2 v2.0.0-rc7 github.com/qdm12/golibs v0.0.0-20210822203818-5c568b0777b6
github.com/qdm12/gosettings v0.4.3 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.2.0 github.com/qdm12/gosplash v0.1.0
github.com/qdm12/gotree v0.3.0 github.com/qdm12/gotree v0.2.0
github.com/qdm12/govalid v0.2.0-rc1
github.com/qdm12/log v0.1.0 github.com/qdm12/log v0.1.0
github.com/qdm12/ss-server v0.6.0 github.com/qdm12/ss-server v0.5.0
github.com/stretchr/testify v1.9.0 github.com/qdm12/updated v0.0.0-20210603204757-205acfe6937e
github.com/stretchr/testify v1.8.4
github.com/ulikunitz/xz v0.5.11 github.com/ulikunitz/xz v0.5.11
github.com/vishvananda/netlink v1.2.1 github.com/vishvananda/netlink v1.2.1-beta.2
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c golang.org/x/net v0.19.0
golang.org/x/net v0.30.0 golang.org/x/sys v0.15.0
golang.org/x/sys v0.26.0 golang.org/x/text v0.14.0
golang.org/x/text v0.19.0 golang.zx2c4.com/wireguard v0.0.0-20230325221338-052af4a8072b
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230215201556-9c5414ab4bde
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
gopkg.in/ini.v1 v1.67.0 gopkg.in/ini.v1 v1.67.0
inet.af/netaddr v0.0.0-20220811202034-502d2d690317
) )
require ( require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/google/go-cmp v0.6.0 // indirect github.com/google/go-cmp v0.5.9 // indirect
github.com/josharian/native v1.1.0 // indirect github.com/josharian/native v1.0.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mdlayher/genetlink v1.3.2 // indirect github.com/mdlayher/genetlink v1.2.0 // indirect
github.com/mdlayher/netlink v1.7.2 // indirect github.com/mdlayher/netlink v1.6.2 // indirect
github.com/mdlayher/socket v0.4.1 // indirect github.com/mdlayher/socket v0.2.3 // indirect
github.com/miekg/dns v1.1.62 // indirect github.com/miekg/dns v1.1.40 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // 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/prometheus/client_golang v1.20.5 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.60.1 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/qdm12/goservices v0.1.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.4 // indirect github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae // indirect
golang.org/x/crypto v0.28.0 // indirect go4.org/intern v0.0.0-20211027215823-ae77deb06f29 // indirect
golang.org/x/mod v0.21.0 // indirect go4.org/unsafe/assume-no-moving-gc v0.0.0-20230525183740-e7c30c78aeb2 // indirect
golang.org/x/sync v0.8.0 // indirect golang.org/x/crypto v0.17.0 // indirect
golang.org/x/tools v0.26.0 // indirect golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
google.golang.org/protobuf v1.35.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
kernel.org/pub/linux/libs/security/libcap/cap v1.2.70 // indirect
kernel.org/pub/linux/libs/security/libcap/psx v1.2.70 // indirect
) )

287
go.sum
View File

@@ -1,161 +1,254 @@
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/breml/rootcerts v0.2.18 h1:KjZaNT7AX/akUjzpStuwTMQs42YHlPyc6NmdwShVba0= github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
github.com/breml/rootcerts v0.2.18/go.mod h1:S/PKh+4d1HUn4HQovEB8hPJZO6pUZYrIhmXBhsegfXw= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/breml/rootcerts v0.2.17 h1:0/M2BE2Apw0qEJCXDOkaiu7d5Sx5ObNfe1BkImJ4u1I=
github.com/breml/rootcerts v0.2.17/go.mod h1:S/PKh+4d1HUn4HQovEB8hPJZO6pUZYrIhmXBhsegfXw=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.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=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw=
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI=
github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=
github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0=
github.com/go-openapi/errors v0.17.2/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0=
github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA=
github.com/go-openapi/runtime v0.17.2/go.mod h1:QO936ZXeisByFmZEO1IS1Dqhtf4QV1sYYFtIq6Ld86Q=
github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=
github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
github.com/go-openapi/validate v0.17.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4=
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/mock v1.6.0 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/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gotify/go-api-client/v2 v2.0.4/go.mod h1:VKiah/UK20bXsr0JObE1eBVLW44zbBouzjuri9iwjFU=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/josharian/native v1.0.0 h1:Ts/E8zCSEsG17dUqv7joXJFybuMLjQfWE04tsBODTxk=
github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/klauspost/compress v1.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 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kyokomi/emoji v2.2.4+incompatible/go.mod h1:mZ6aGCD7yk8j6QY6KICwnZ2pxoszVseX1DNoGtU2tBA=
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mdlayher/genetlink v1.3.2 h1:KdrNKe+CTu+IbZnm/GVUMXSqBBLqcGpRDa0xkQy56gw= github.com/mdlayher/genetlink v1.2.0 h1:4yrIkRV5Wfk1WfpWTcoOlGmsWgQj3OtQN9ZsbrE+XtU=
github.com/mdlayher/genetlink v1.3.2/go.mod h1:tcC3pkCrPUGIKKsCsp0B3AdaaKuHtaxoJRz3cc+528o= github.com/mdlayher/genetlink v1.2.0/go.mod h1:ra5LDov2KrUCZJiAtEvXXZBxGMInICMXIwshlJ+qRxQ=
github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g= github.com/mdlayher/netlink v1.6.0/go.mod h1:0o3PlBmGst1xve7wQ7j/hwpNaFaH4qCRyWCdcZk8/vA=
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= github.com/mdlayher/netlink v1.6.2 h1:D2zGSkvYsJ6NreeED3JiVTu1lj2sIYATqSaZlhPzUgQ=
github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U= github.com/mdlayher/netlink v1.6.2/go.mod h1:O1HXX2sIWSMJ3Qn1BYZk1yZM+7iMki/uYGGiwGyq/iU=
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= github.com/mdlayher/socket v0.1.1/go.mod h1:mYV5YIZAfHh4dzDVzI8x8tWLWCliuX8Mon5Awbj+qDs=
github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ= github.com/mdlayher/socket v0.2.3 h1:XZA2X2TjdOwNoNPVPclRCURoX/hokBY8nkTmRZFEheM=
github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ= github.com/mdlayher/socket v0.2.3/go.mod h1:bz12/FozYNH/VbvC3q7TRIK/Y6dH1kCKsXaUeXi/FmY=
github.com/miekg/dns v1.1.40 h1:pyyPFfGMnciYUk/mXpKkVmeMQjfXqt3FAJ2hy7tPiLA=
github.com/miekg/dns v1.1.40/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721 h1:RlZweED6sbSArvlE924+mUcZuXKLBHA35U7LN621Bws= github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721 h1:RlZweED6sbSArvlE924+mUcZuXKLBHA35U7LN621Bws=
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721/go.mod h1:Ickgr2WtCLZ2MDGd4Gr0geeCH5HybhRJbonOgQpvSxc= github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721/go.mod h1:Ickgr2WtCLZ2MDGd4Gr0geeCH5HybhRJbonOgQpvSxc=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo=
github.com/phayes/permbits v0.0.0-20190612203442-39d7c581d2ee/go.mod h1:3uODdxMgOaPYeWU7RzZLxVtJHZ/x1f/iHkBZuKJDzuY=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= github.com/qdm12/dns v1.11.0 h1:jpcD5DZXXQSQe5a263PL09ghukiIdptvXFOZvyKEm6Q=
github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/qdm12/dns v1.11.0/go.mod h1:FmQsNOUcrrZ4UFzWAiED56AKXeNgaX3ySbmPwEfNjjE=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/qdm12/golibs v0.0.0-20210603202746-e5494e9c2ebb/go.mod h1:15RBzkun0i8XB7ADIoLJWp9ITRgsz3LroEI2FiOXLRg=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/qdm12/golibs v0.0.0-20210723175634-a75ca7fd74c2/go.mod h1:6aRbg4Z/bTbm9JfxsGXfWKHi7zsOvPfUTK1S5HuAFKg=
github.com/prometheus/common v0.60.1 h1:FUas6GcOw66yB/73KC+BOZoFJmbo/1pojoILArPAaSc= github.com/qdm12/golibs v0.0.0-20210822203818-5c568b0777b6 h1:bge5AL7cjHJMPz+5IOz5yF01q/l8No6+lIEBieA8gMg=
github.com/prometheus/common v0.60.1/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw= github.com/qdm12/golibs v0.0.0-20210822203818-5c568b0777b6/go.mod h1:6aRbg4Z/bTbm9JfxsGXfWKHi7zsOvPfUTK1S5HuAFKg=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/qdm12/gosettings v0.4.0-rc1 h1:UYA92yyeDPbmZysIuG65yrpZVPtdIoRmtEHft/AyI38=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/qdm12/gosettings v0.4.0-rc1/go.mod h1:JRV3opOpHvnKlIA29lKQMdYw1WSMVMfHYLLHPHol5ME=
github.com/qdm12/dns/v2 v2.0.0-rc7 h1:noFoWunMfFuJeIpcyTJsdS9BU7jRxxKrBV5xN2qjPHI=
github.com/qdm12/dns/v2 v2.0.0-rc7/go.mod h1:VaF02KWEL7xNV4oKfG4N9nEv/kR6bqyIcBReCV5NJhw=
github.com/qdm12/goservices v0.1.0 h1:9sODefm/yuIGS7ynCkEnNlMTAYn9GzPhtcK4F69JWvc=
github.com/qdm12/goservices v0.1.0/go.mod h1:/JOFsAnHFiSjyoXxa5FlfX903h20K5u/3rLzCjYVMck=
github.com/qdm12/gosettings v0.4.3 h1:oGAjiKVtml9oHVlPQo6H3yk6TmtWpVYicNeGFcM7AP8=
github.com/qdm12/gosettings v0.4.3/go.mod h1:CPrt2YC4UsURTrslmhxocVhMCW03lIrqdH2hzIf5prg=
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.2.0 h1:DOxCEizbW6ZG+FgpH2oK1atT6bM8MHL9GZ2ywSS4zZY= github.com/qdm12/gosplash v0.1.0 h1:Sfl+zIjFZFP7b0iqf2l5UkmEY97XBnaKkH3FNY6Gf7g=
github.com/qdm12/gosplash v0.2.0/go.mod h1:k+1PzhO0th9cpX4q2Nneu4xTsndXqrM/x7NTIYmJ4jo= github.com/qdm12/gosplash v0.1.0/go.mod h1:+A3fWW4/rUeDXhY3ieBzwghKdnIPFJgD8K3qQkenJlw=
github.com/qdm12/gotree v0.3.0 h1:Q9f4C571EFK7ZEsPkEL2oGZX7I+ZhVxhh1ZSydW+5yI= github.com/qdm12/gotree v0.2.0 h1:+58ltxkNLUyHtATFereAcOjBVfY6ETqRex8XK90Fb/c=
github.com/qdm12/gotree v0.3.0/go.mod h1:iz06uXmRR4Aq9v6tX7mosXStO/yGHxRA1hbyD0UVeYw= 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.2.0-rc1/go.mod h1:/uWzVWMuS71wmbsVnlUxpQiy6EAXqm8eQ2RbyA72roQ=
github.com/qdm12/log v0.1.0 h1:jYBd/xscHYpblzZAd2kjZp2YmuYHjAAfbTViJWxoPTw= github.com/qdm12/log v0.1.0 h1:jYBd/xscHYpblzZAd2kjZp2YmuYHjAAfbTViJWxoPTw=
github.com/qdm12/log v0.1.0/go.mod h1:Vchi5M8uBvHfPNIblN4mjXn/oSbiWguQIbsgF1zdQPI= github.com/qdm12/log v0.1.0/go.mod h1:Vchi5M8uBvHfPNIblN4mjXn/oSbiWguQIbsgF1zdQPI=
github.com/qdm12/ss-server v0.6.0 h1:OaOdCIBXx0z3DGHPT6Th0v88vGa3MtAS4oRgUsDHGZE= github.com/qdm12/ss-server v0.5.0 h1:ARAqJayohDM51BmJ/R5Yplkpo+Qxgp7xizBF1HWd7uQ=
github.com/qdm12/ss-server v0.6.0/go.mod h1:0BO/zEmtTiLDlmQEcjtoHTC+w+cWxwItjBuGP6TWM78= 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/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=
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s= github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 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/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8= 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/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/vishvananda/netlink v1.2.1 h1:pfLv/qlJUwOTPvtWREA7c3PI4u81YkqZw1DYhI2HmLA= github.com/vishvananda/netlink v1.2.1-beta.2 h1:Llsql0lnQEbHj0I1OuKyp8otXp0r3q0mPkuhwHfStVs=
github.com/vishvananda/netlink v1.2.1/go.mod h1:i6NetklAujEcC6fK0JPjT8qSwWyO0HLn4UKG+hGqeJs= github.com/vishvananda/netlink v1.2.1-beta.2/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8= github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae h1:4hwBBUfQCFe3Cym0ZtKyq7L16eZUtYKs+BaHDN6mAns=
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
github.com/yl2chen/cidranger v1.0.2/go.mod h1:9U1yz7WPYDwf0vpNWFaeRh0bjwz5RVgRy/9UEQfHl0g=
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a h1:fZHgsYlfvtyqToslyjUt3VOPF4J7aK/3MPcK7xp3PDk= github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a h1:fZHgsYlfvtyqToslyjUt3VOPF4J7aK/3MPcK7xp3PDk=
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a/go.mod h1:ul22v+Nro/R083muKhosV54bj5niojjWZvU8xrevuH4= github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a/go.mod h1:ul22v+Nro/R083muKhosV54bj5niojjWZvU8xrevuH4=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
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-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-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-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-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8= 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.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/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
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-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
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.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220923203811-8be639271d50/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/net v0.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-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.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.0.0-20220923202941-7f9b1623fab7/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 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-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-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-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-20210119212857-b64e53b001e4/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-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-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.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.26.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.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 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-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
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-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/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4= golang.zx2c4.com/wireguard v0.0.0-20230325221338-052af4a8072b h1:J1CaxgLerRR5lgx3wnr6L04cJFbWoceSK9JWBdglINo=
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA= golang.zx2c4.com/wireguard v0.0.0-20230325221338-052af4a8072b/go.mod h1:tqur9LnfstdR9ep2LaJT4lFUl0EjlHtge+gAjmsHUG4=
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 h1:CawjfCvYQH2OU3/TnxLx97WDSUDRABfT18pCOYwc2GE= golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230215201556-9c5414ab4bde h1:ybF7AMzIUikL9x4LgwEmzhXtzRpKNqngme1VGDWz+Nk=
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6/go.mod h1:3rxYc4HtVcSG9gVaTs2GEBdehh+sYPOwKtyUWEOTb80= golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230215201556-9c5414ab4bde/go.mod h1:mQqgjkW8GQQcJQsbBvK890TKqUK1DfKWkuBGbOkuMHQ=
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
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-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 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 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98=
gopkg.in/src-d/go-git-fixtures.v3 v3.5.0/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g=
gopkg.in/src-d/go-git.v4 v4.13.1/go.mod h1:nx5NYcxdKxq5fpltdHnPa2Exj4Sx0EclMWZQbYDu2z8=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259 h1:TbRPT0HtzFP3Cno1zZo7yPzEEnfu8EjLfl6IU9VfqkQ= gvisor.dev/gvisor v0.0.0-20221203005347-703fd9b7fbc0 h1:Wobr37noukisGxpKo5jAsLREcpj61RxrWYzD8uwveOY=
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259/go.mod h1:AVgIgHMwK63XvmAzWG9vLQ41YnVHN0du0tEC46fI7yY= gvisor.dev/gvisor v0.0.0-20221203005347-703fd9b7fbc0/go.mod h1:Dn5idtptoW1dIos9U6A2rpebLs/MtTwFacjKb8jLdQA=
kernel.org/pub/linux/libs/security/libcap/cap v1.2.70 h1:QnLPkuDWWbD5C+3DUA2IUXai5TK6w2zff+MAGccqdsw= inet.af/netaddr v0.0.0-20210511181906-37180328850c/go.mod h1:z0nx+Dh+7N7CC8V5ayHtHGpZpxLQZZxkIaaz6HN65Ls=
kernel.org/pub/linux/libs/security/libcap/cap v1.2.70/go.mod h1:/iBwcj9nbLejQitYvUm9caurITQ6WyNHibJk6Q9fiS4= inet.af/netaddr v0.0.0-20220811202034-502d2d690317 h1:U2fwK6P2EqmopP/hFLTOAjWTki0qgd4GMJn5X8wOleU=
kernel.org/pub/linux/libs/security/libcap/psx v1.2.70 h1:HsB2G/rEQiYyo1bGoQqHZ/Bvd6x1rERQTNdPr1FyWjI= inet.af/netaddr v0.0.0-20220811202034-502d2d690317/go.mod h1:OIezDfdzOgFhuw4HuWapWq2e9l0H9tK4F1j+ETRtF3k=
kernel.org/pub/linux/libs/security/libcap/psx v1.2.70/go.mod h1:+l6Ee2F59XiJ2I6WR5ObpC1utCQJZ/VLsEbQCD8RG24=

View File

@@ -3,13 +3,14 @@ package alpine
import ( import (
"errors" "errors"
"fmt" "fmt"
"io/fs"
"os" "os"
"os/user" "os/user"
"strconv" "strconv"
) )
var ErrUserAlreadyExists = errors.New("user already exists") var (
ErrUserAlreadyExists = errors.New("user already exists")
)
// 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) {
@@ -38,8 +39,7 @@ func (a *Alpine) CreateUser(username string, uid int) (createdUsername string, e
ErrUserAlreadyExists, username, u.Uid, uid) ErrUserAlreadyExists, username, u.Uid, uid)
} }
const permission = fs.FileMode(0o644) file, err := os.OpenFile(a.passwdPath, os.O_APPEND|os.O_WRONLY, 0644)
file, err := os.OpenFile(a.passwdPath, os.O_APPEND|os.O_WRONLY, permission)
if err != nil { if err != nil {
return "", err return "", err
} }

View File

@@ -6,12 +6,13 @@ import (
"io" "io"
"os" "os"
"strings" "strings"
"github.com/qdm12/gluetun/internal/configuration/sources/files"
) )
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)
const openVPNClientKeyPath = "/gluetun/client.key" // TODO deduplicate? filepath := flagSet.String("path", files.OpenVPNClientKeyPath, "file path to the client.key file")
filepath := flagSet.String("path", openVPNClientKeyPath, "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
} }
@@ -27,6 +28,9 @@ func (c *CLI) ClientKey(args []string) error {
if err := file.Close(); err != nil { if err := file.Close(); err != nil {
return err return err
} }
if err != nil {
return err
}
s := string(data) s := string(data)
s = strings.ReplaceAll(s, "\n", "") s = strings.ReplaceAll(s, "\n", "")
s = strings.ReplaceAll(s, "\r", "") s = strings.ReplaceAll(s, "\r", "")

View File

@@ -4,7 +4,6 @@ import (
"errors" "errors"
"flag" "flag"
"fmt" "fmt"
"io/fs"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
@@ -17,13 +16,13 @@ import (
) )
var ( var (
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") ErrMultipleProvidersToFormat = errors.New("more than one VPN provider to format were specified")
) )
func addProviderFlag(flagSet *flag.FlagSet, providerToFormat map[string]*bool, func addProviderFlag(flagSet *flag.FlagSet, providerToFormat map[string]*bool,
provider string, titleCaser cases.Caser, provider string, titleCaser cases.Caser) {
) {
boolPtr, ok := providerToFormat[provider] boolPtr, ok := providerToFormat[provider]
if !ok { if !ok {
panic(fmt.Sprintf("unknown provider in format map: %s", provider)) panic(fmt.Sprintf("unknown provider in format map: %s", provider))
@@ -44,7 +43,7 @@ func (c *CLI) FormatServers(args []string) error {
providersToFormat[provider] = new(bool) providersToFormat[provider] = new(bool)
} }
flagSet := flag.NewFlagSet("format-servers", flag.ExitOnError) flagSet := flag.NewFlagSet("format-servers", flag.ExitOnError)
flagSet.StringVar(&format, "format", "markdown", "Format to use which can be: 'markdown' or 'json'") 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) titleCaser := cases.Title(language.English)
for _, provider := range allProviderFlags { for _, provider := range allProviderFlags {
@@ -54,7 +53,9 @@ func (c *CLI) FormatServers(args []string) error {
return err return err
} }
// Note the format is validated by storage.Format if format != "markdown" {
return fmt.Errorf("%w: %s", ErrFormatNotRecognized, format)
}
// Verify only one provider is set to be formatted. // Verify only one provider is set to be formatted.
var providers []string var providers []string
@@ -86,14 +87,10 @@ func (c *CLI) FormatServers(args []string) error {
return fmt.Errorf("creating servers storage: %w", err) return fmt.Errorf("creating servers storage: %w", err)
} }
formatted, err := storage.Format(providerToFormat, format) formatted := storage.FormatToMarkdown(providerToFormat)
if err != nil {
return fmt.Errorf("formatting servers: %w", err)
}
output = filepath.Clean(output) output = filepath.Clean(output)
const permission = fs.FileMode(0o644) 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, permission)
if err != nil { if err != nil {
return fmt.Errorf("opening output file: %w", err) return fmt.Errorf("opening output file: %w", err)
} }

View File

@@ -1,66 +0,0 @@
package cli
import (
"crypto/rand"
"flag"
"fmt"
)
func (c *CLI) GenKey(args []string) (err error) {
flagSet := flag.NewFlagSet("genkey", flag.ExitOnError)
err = flagSet.Parse(args)
if err != nil {
return fmt.Errorf("parsing flags: %w", err)
}
const keyLength = 128 / 8
keyBytes := make([]byte, keyLength)
_, _ = rand.Read(keyBytes)
key := base58Encode(keyBytes)
fmt.Println(key)
return nil
}
func base58Encode(data []byte) string {
const alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
const radix = 58
zcount := 0
for zcount < len(data) && data[zcount] == 0 {
zcount++
}
// integer simplification of ceil(log(256)/log(58))
ceilLog256Div58 := (len(data)-zcount)*555/406 + 1 //nolint:mnd
size := zcount + ceilLog256Div58
output := make([]byte, size)
high := size - 1
for _, b := range data {
i := size - 1
for carry := uint32(b); i > high || carry != 0; i-- {
carry += 256 * uint32(output[i]) //nolint:mnd
output[i] = byte(carry % radix)
carry /= radix
}
high = i
}
// Determine the additional "zero-gap" in the output buffer
additionalZeroGapEnd := zcount
for additionalZeroGapEnd < size && output[additionalZeroGapEnd] == 0 {
additionalZeroGapEnd++
}
val := output[additionalZeroGapEnd-zcount:]
size = len(val)
for i := range val {
output[i] = alphabet[val[i]]
}
return string(output[:size])
}

View File

@@ -6,15 +6,12 @@ import (
"net/http" "net/http"
"time" "time"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/healthcheck" "github.com/qdm12/gluetun/internal/healthcheck"
"github.com/qdm12/gosettings/reader"
) )
func (c *CLI) HealthCheck(ctx context.Context, reader *reader.Reader, _ Warner) (err error) { func (c *CLI) HealthCheck(ctx context.Context, source Source, _ Warner) error {
// Extract the health server port from the configuration. // Extract the health server port from the configuration.
var config settings.Health config, err := source.ReadHealth()
err = config.Read(reader)
if err != nil { if err != nil {
return err return err
} }

View File

@@ -1,9 +1,16 @@
package cli package cli
import "github.com/qdm12/golibs/logging"
type noopLogger struct{} type noopLogger struct{}
func newNoopLogger() *noopLogger { func newNoopLogger() *noopLogger {
return new(noopLogger) return new(noopLogger)
} }
func (l *noopLogger) Debug(string) {}
func (l *noopLogger) Info(string) {} func (l *noopLogger) Info(string) {}
func (l *noopLogger) Warn(string) {}
func (l *noopLogger) Error(string) {}
func (l *noopLogger) PatchLevel(logging.Level) {}
func (l *noopLogger) PatchPrefix(string) {}

View File

@@ -8,14 +8,12 @@ import (
"strings" "strings"
"time" "time"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/models" "github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/openvpn/extract" "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" "github.com/qdm12/gluetun/internal/updater/resolver"
"github.com/qdm12/gosettings/reader"
) )
type OpenvpnConfigLogger interface { type OpenvpnConfigLogger interface {
@@ -34,8 +32,6 @@ type ParallelResolver interface {
} }
type IPFetcher interface { type IPFetcher interface {
String() string
CanFetchAnyIP() bool
FetchInfo(ctx context.Context, ip netip.Addr) (data models.PublicIP, err error) FetchInfo(ctx context.Context, ip netip.Addr) (data models.PublicIP, err error)
} }
@@ -43,16 +39,14 @@ type IPv6Checker interface {
IsIPv6Supported() (supported bool, err error) IsIPv6Supported() (supported bool, err error)
} }
func (c *CLI) OpenvpnConfig(logger OpenvpnConfigLogger, reader *reader.Reader, func (c *CLI) OpenvpnConfig(logger OpenvpnConfigLogger, source Source,
ipv6Checker IPv6Checker, ipv6Checker IPv6Checker) error {
) 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
} }
var allSettings settings.Settings allSettings, err := source.Read()
err = allSettings.Read(reader, logger)
if err != nil { if err != nil {
return err return err
} }
@@ -62,7 +56,7 @@ func (c *CLI) OpenvpnConfig(logger OpenvpnConfigLogger, reader *reader.Reader,
return fmt.Errorf("checking for IPv6 support: %w", err) return fmt.Errorf("checking for IPv6 support: %w", err)
} }
if err = allSettings.Validate(storage, ipv6Supported, logger); err != nil { if err = allSettings.Validate(storage, ipv6Supported); err != nil {
return fmt.Errorf("validating settings: %w", err) return fmt.Errorf("validating settings: %w", err)
} }
@@ -76,7 +70,7 @@ func (c *CLI) OpenvpnConfig(logger OpenvpnConfigLogger, reader *reader.Reader,
providers := provider.NewProviders(storage, time.Now, warner, client, providers := provider.NewProviders(storage, time.Now, warner, client,
unzipper, parallelResolver, ipFetcher, openvpnFileExtractor) unzipper, parallelResolver, ipFetcher, openvpnFileExtractor)
providerConf := providers.Get(allSettings.VPN.Provider.Name) providerConf := providers.Get(*allSettings.VPN.Provider.Name)
connection, err := providerConf.GetConnection( connection, err := providerConf.GetConnection(
allSettings.VPN.Provider.ServerSelection, ipv6Supported) allSettings.VPN.Provider.ServerSelection, ipv6Supported)
if err != nil { if err != nil {

View File

@@ -80,17 +80,10 @@ func (c *CLI) Update(ctx context.Context, args []string, logger UpdaterLogger) e
httpClient := &http.Client{Timeout: clientTimeout} httpClient := &http.Client{Timeout: clientTimeout}
unzipper := unzip.New(httpClient) unzipper := unzip.New(httpClient)
parallelResolver := resolver.NewParallelResolver(options.DNSAddress) parallelResolver := resolver.NewParallelResolver(options.DNSAddress)
nameTokenPairs := []api.NameToken{ ipFetcher, err := api.New(api.IPInfo, httpClient, ipToken)
{Name: string(api.IPInfo), Token: ipToken},
{Name: string(api.IP2Location)},
{Name: string(api.IfConfigCo)},
}
fetchers, err := api.New(nameTokenPairs, httpClient)
if err != nil { if err != nil {
return fmt.Errorf("creating public IP fetchers: %w", err) return fmt.Errorf("creating public IP API client: %w", err)
} }
ipFetcher := api.NewResilient(fetchers, logger)
openvpnFileExtractor := extract.New() openvpnFileExtractor := extract.New()
providers := provider.NewProviders(storage, time.Now, logger, httpClient, providers := provider.NewProviders(storage, time.Now, logger, httpClient,

View File

@@ -1,8 +0,0 @@
package command
// Cmder handles running subprograms synchronously and asynchronously.
type Cmder struct{}
func New() *Cmder {
return &Cmder{}
}

View File

@@ -1,11 +0,0 @@
package command
import "io"
type execCmd interface {
CombinedOutput() ([]byte, error)
StdoutPipe() (io.ReadCloser, error)
StderrPipe() (io.ReadCloser, error)
Start() error
Wait() error
}

View File

@@ -1,3 +0,0 @@
package command
//go:generate mockgen -destination=mocks_local_test.go -package=$GOPACKAGE -source=interfaces_local.go

View File

@@ -1,108 +0,0 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: interfaces_local.go
// Package command is a generated GoMock package.
package command
import (
io "io"
reflect "reflect"
gomock "github.com/golang/mock/gomock"
)
// MockexecCmd is a mock of execCmd interface.
type MockexecCmd struct {
ctrl *gomock.Controller
recorder *MockexecCmdMockRecorder
}
// MockexecCmdMockRecorder is the mock recorder for MockexecCmd.
type MockexecCmdMockRecorder struct {
mock *MockexecCmd
}
// NewMockexecCmd creates a new mock instance.
func NewMockexecCmd(ctrl *gomock.Controller) *MockexecCmd {
mock := &MockexecCmd{ctrl: ctrl}
mock.recorder = &MockexecCmdMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockexecCmd) EXPECT() *MockexecCmdMockRecorder {
return m.recorder
}
// CombinedOutput mocks base method.
func (m *MockexecCmd) CombinedOutput() ([]byte, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CombinedOutput")
ret0, _ := ret[0].([]byte)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// CombinedOutput indicates an expected call of CombinedOutput.
func (mr *MockexecCmdMockRecorder) CombinedOutput() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CombinedOutput", reflect.TypeOf((*MockexecCmd)(nil).CombinedOutput))
}
// Start mocks base method.
func (m *MockexecCmd) Start() error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Start")
ret0, _ := ret[0].(error)
return ret0
}
// Start indicates an expected call of Start.
func (mr *MockexecCmdMockRecorder) Start() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Start", reflect.TypeOf((*MockexecCmd)(nil).Start))
}
// StderrPipe mocks base method.
func (m *MockexecCmd) StderrPipe() (io.ReadCloser, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "StderrPipe")
ret0, _ := ret[0].(io.ReadCloser)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// StderrPipe indicates an expected call of StderrPipe.
func (mr *MockexecCmdMockRecorder) StderrPipe() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StderrPipe", reflect.TypeOf((*MockexecCmd)(nil).StderrPipe))
}
// StdoutPipe mocks base method.
func (m *MockexecCmd) StdoutPipe() (io.ReadCloser, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "StdoutPipe")
ret0, _ := ret[0].(io.ReadCloser)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// StdoutPipe indicates an expected call of StdoutPipe.
func (mr *MockexecCmdMockRecorder) StdoutPipe() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StdoutPipe", reflect.TypeOf((*MockexecCmd)(nil).StdoutPipe))
}
// Wait mocks base method.
func (m *MockexecCmd) Wait() error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Wait")
ret0, _ := ret[0].(error)
return ret0
}
// Wait indicates an expected call of Wait.
func (mr *MockexecCmdMockRecorder) Wait() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Wait", reflect.TypeOf((*MockexecCmd)(nil).Wait))
}

View File

@@ -1,30 +0,0 @@
package command
import (
"os/exec"
"strings"
)
// Run runs a command in a blocking manner, returning its output and
// an error if it failed.
func (c *Cmder) Run(cmd *exec.Cmd) (output string, err error) {
return run(cmd)
}
func run(cmd execCmd) (output string, err error) {
stdout, err := cmd.CombinedOutput()
output = string(stdout)
output = strings.TrimSuffix(output, "\n")
lines := stringToLines(output)
for i := range lines {
lines[i] = strings.TrimPrefix(lines[i], "'")
lines[i] = strings.TrimSuffix(lines[i], "'")
}
output = strings.Join(lines, "\n")
return output, err
}
func stringToLines(s string) (lines []string) {
s = strings.TrimSuffix(s, "\n")
return strings.Split(s, "\n")
}

View File

@@ -1,54 +0,0 @@
package command
import (
"errors"
"testing"
gomock "github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_run(t *testing.T) {
t.Parallel()
errDummy := errors.New("dummy")
testCases := map[string]struct {
stdout []byte
cmdErr error
output string
err error
}{
"no output": {},
"cmd error": {
stdout: []byte("'hello \nworld'\n"),
cmdErr: errDummy,
output: "hello \nworld",
err: errDummy,
},
}
for name, testCase := range testCases {
t.Run(name, func(t *testing.T) {
t.Parallel()
ctrl := gomock.NewController(t)
mockCmd := NewMockexecCmd(ctrl)
mockCmd.EXPECT().CombinedOutput().Return(testCase.stdout, testCase.cmdErr)
output, err := run(mockCmd)
if testCase.err != nil {
require.Error(t, err)
assert.Equal(t, testCase.err.Error(), err.Error())
} else {
assert.NoError(t, err)
}
assert.Equal(t, testCase.output, output)
})
}
}

View File

@@ -1,100 +0,0 @@
package command
import (
"bufio"
"errors"
"io"
"os"
"os/exec"
)
// Start launches a command and streams stdout and stderr to channels.
// All the channels returned are ready only and won't be closed
// if the command fails later.
func (c *Cmder) Start(cmd *exec.Cmd) (
stdoutLines, stderrLines <-chan string,
waitError <-chan error, startErr error,
) {
return start(cmd)
}
func start(cmd execCmd) (stdoutLines, stderrLines <-chan string,
waitError <-chan error, startErr error,
) {
stop := make(chan struct{})
stdoutReady := make(chan struct{})
stdoutLinesCh := make(chan string)
stdoutDone := make(chan struct{})
stderrReady := make(chan struct{})
stderrLinesCh := make(chan string)
stderrDone := make(chan struct{})
stdout, err := cmd.StdoutPipe()
if err != nil {
return nil, nil, nil, err
}
go streamToChannel(stdoutReady, stop, stdoutDone, stdout, stdoutLinesCh)
stderr, err := cmd.StderrPipe()
if err != nil {
_ = stdout.Close()
close(stop)
<-stdoutDone
return nil, nil, nil, err
}
go streamToChannel(stderrReady, stop, stderrDone, stderr, stderrLinesCh)
err = cmd.Start()
if err != nil {
_ = stdout.Close()
_ = stderr.Close()
close(stop)
<-stdoutDone
<-stderrDone
return nil, nil, nil, err
}
waitErrorCh := make(chan error)
go func() {
err := cmd.Wait()
_ = stdout.Close()
_ = stderr.Close()
close(stop)
<-stdoutDone
<-stderrDone
waitErrorCh <- err
}()
return stdoutLinesCh, stderrLinesCh, waitErrorCh, nil
}
func streamToChannel(ready chan<- struct{},
stop <-chan struct{}, done chan<- struct{},
stream io.Reader, lines chan<- string,
) {
defer close(done)
close(ready)
scanner := bufio.NewScanner(stream)
lineBuffer := make([]byte, bufio.MaxScanTokenSize) // 64KB
const maxCapacity = 20 * 1024 * 1024 // 20MB
scanner.Buffer(lineBuffer, maxCapacity)
for scanner.Scan() {
// scanner is closed if the context is canceled
// or if the command failed starting because the
// stream is closed (io.EOF error).
lines <- scanner.Text()
}
err := scanner.Err()
if err == nil || errors.Is(err, os.ErrClosed) {
return
}
// ignore the error if it is stopped.
select {
case <-stop:
return
default:
lines <- "stream error: " + err.Error()
}
}

View File

@@ -1,118 +0,0 @@
package command
import (
"bytes"
"errors"
"io"
"strings"
"testing"
gomock "github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func linesToReadCloser(lines []string) io.ReadCloser {
s := strings.Join(lines, "\n")
return io.NopCloser(bytes.NewBufferString(s))
}
func Test_start(t *testing.T) {
t.Parallel()
errDummy := errors.New("dummy")
testCases := map[string]struct {
stdout []string
stdoutPipeErr error
stderr []string
stderrPipeErr error
startErr error
waitErr error
err error
}{
"no output": {},
"success": {
stdout: []string{"hello", "world"},
stderr: []string{"some", "error"},
},
"stdout pipe error": {
stdoutPipeErr: errDummy,
err: errDummy,
},
"stderr pipe error": {
stderrPipeErr: errDummy,
err: errDummy,
},
"start error": {
startErr: errDummy,
err: errDummy,
},
"wait error": {
waitErr: errDummy,
},
}
for name, testCase := range testCases {
t.Run(name, func(t *testing.T) {
t.Parallel()
ctrl := gomock.NewController(t)
stdout := linesToReadCloser(testCase.stdout)
stderr := linesToReadCloser(testCase.stderr)
mockCmd := NewMockexecCmd(ctrl)
mockCmd.EXPECT().StdoutPipe().
Return(stdout, testCase.stdoutPipeErr)
if testCase.stdoutPipeErr == nil {
mockCmd.EXPECT().StderrPipe().Return(stderr, testCase.stderrPipeErr)
if testCase.stderrPipeErr == nil {
mockCmd.EXPECT().Start().Return(testCase.startErr)
if testCase.startErr == nil {
mockCmd.EXPECT().Wait().Return(testCase.waitErr)
}
}
}
stdoutLines, stderrLines, waitError, err := start(mockCmd)
if testCase.err != nil {
require.Error(t, err)
assert.Equal(t, testCase.err.Error(), err.Error())
assert.Nil(t, stdoutLines)
assert.Nil(t, stderrLines)
assert.Nil(t, waitError)
return
}
require.NoError(t, err)
var stdoutIndex, stderrIndex int
done := false
for !done {
select {
case line := <-stdoutLines:
assert.Equal(t, testCase.stdout[stdoutIndex], line)
stdoutIndex++
case line := <-stderrLines:
assert.Equal(t, testCase.stderr[stderrIndex], line)
stderrIndex++
case err := <-waitError:
if testCase.waitErr != nil {
require.Error(t, err)
assert.Equal(t, testCase.waitErr.Error(), err.Error())
} else {
assert.NoError(t, err)
}
done = true
}
}
assert.Equal(t, len(testCase.stdout), stdoutIndex)
assert.Equal(t, len(testCase.stderr), stderrIndex)
})
}
}

View File

@@ -1,25 +0,0 @@
package settings
import (
"slices"
"github.com/qdm12/gosettings/reader"
"golang.org/x/exp/maps"
)
func readObsolete(r *reader.Reader) (warnings []string) {
keyToMessage := map[string]string{
"DOT_VERBOSITY": "DOT_VERBOSITY is obsolete, use LOG_LEVEL instead.",
"DOT_VERBOSITY_DETAILS": "DOT_VERBOSITY_DETAILS is obsolete because it was specific to Unbound.",
"DOT_VALIDATION_LOGLEVEL": "DOT_VALIDATION_LOGLEVEL is obsolete because DNSSEC validation is not implemented.",
}
sortedKeys := maps.Keys(keyToMessage)
slices.Sort(sortedKeys)
warnings = make([]string, 0, len(keyToMessage))
for _, key := range sortedKeys {
if r.Get(key) != nil {
warnings = append(warnings, keyToMessage[key])
}
}
return warnings
}

View File

@@ -5,7 +5,6 @@ import (
"net/netip" "net/netip"
"github.com/qdm12/gosettings" "github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/reader"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
) )
@@ -50,6 +49,14 @@ func (d *DNS) Copy() (copied DNS) {
} }
} }
// mergeWith merges the other settings into any
// unset field of the receiver settings object.
func (d *DNS) mergeWith(other DNS) {
d.ServerAddress = gosettings.MergeWithValidator(d.ServerAddress, other.ServerAddress)
d.KeepNameserver = gosettings.MergeWithPointer(d.KeepNameserver, other.KeepNameserver)
d.DoT.mergeWith(other.DoT)
}
// 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.
@@ -80,22 +87,3 @@ func (d DNS) toLinesNode() (node *gotree.Node) {
node.AppendNode(d.DoT.toLinesNode()) node.AppendNode(d.DoT.toLinesNode())
return node return node
} }
func (d *DNS) read(r *reader.Reader) (err error) {
d.ServerAddress, err = r.NetipAddr("DNS_ADDRESS", reader.RetroKeys("DNS_PLAINTEXT_ADDRESS"))
if err != nil {
return err
}
d.KeepNameserver, err = r.BoolPtr("DNS_KEEP_NAMESERVER")
if err != nil {
return err
}
err = d.DoT.read(r)
if err != nil {
return fmt.Errorf("DNS over TLS settings: %w", err)
}
return nil
}

View File

@@ -3,13 +3,11 @@ package settings
import ( import (
"errors" "errors"
"fmt" "fmt"
"net/http"
"net/netip" "net/netip"
"regexp" "regexp"
"github.com/qdm12/dns/v2/pkg/blockbuilder" "github.com/qdm12/dns/pkg/blacklist"
"github.com/qdm12/gosettings" "github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/reader"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
) )
@@ -65,6 +63,16 @@ func (b DNSBlacklist) copy() (copied DNSBlacklist) {
} }
} }
func (b *DNSBlacklist) mergeWith(other DNSBlacklist) {
b.BlockMalicious = gosettings.MergeWithPointer(b.BlockMalicious, other.BlockMalicious)
b.BlockAds = gosettings.MergeWithPointer(b.BlockAds, other.BlockAds)
b.BlockSurveillance = gosettings.MergeWithPointer(b.BlockSurveillance, other.BlockSurveillance)
b.AllowedHosts = gosettings.MergeWithSlice(b.AllowedHosts, other.AllowedHosts)
b.AddBlockedHosts = gosettings.MergeWithSlice(b.AddBlockedHosts, other.AddBlockedHosts)
b.AddBlockedIPs = gosettings.MergeWithSlice(b.AddBlockedIPs, other.AddBlockedIPs)
b.AddBlockedIPPrefixes = gosettings.MergeWithSlice(b.AddBlockedIPPrefixes, other.AddBlockedIPPrefixes)
}
func (b *DNSBlacklist) overrideWith(other DNSBlacklist) { func (b *DNSBlacklist) overrideWith(other DNSBlacklist) {
b.BlockMalicious = gosettings.OverrideWithPointer(b.BlockMalicious, other.BlockMalicious) b.BlockMalicious = gosettings.OverrideWithPointer(b.BlockMalicious, other.BlockMalicious)
b.BlockAds = gosettings.OverrideWithPointer(b.BlockAds, other.BlockAds) b.BlockAds = gosettings.OverrideWithPointer(b.BlockAds, other.BlockAds)
@@ -75,19 +83,16 @@ func (b *DNSBlacklist) overrideWith(other DNSBlacklist) {
b.AddBlockedIPPrefixes = gosettings.OverrideWithSlice(b.AddBlockedIPPrefixes, other.AddBlockedIPPrefixes) b.AddBlockedIPPrefixes = gosettings.OverrideWithSlice(b.AddBlockedIPPrefixes, other.AddBlockedIPPrefixes)
} }
func (b DNSBlacklist) ToBlockBuilderSettings(client *http.Client) ( func (b DNSBlacklist) ToBlacklistFormat() (settings blacklist.BuilderSettings, err error) {
settings blockbuilder.Settings, return blacklist.BuilderSettings{
) { BlockMalicious: *b.BlockMalicious,
return blockbuilder.Settings{ BlockAds: *b.BlockAds,
Client: client, BlockSurveillance: *b.BlockSurveillance,
BlockMalicious: b.BlockMalicious,
BlockAds: b.BlockAds,
BlockSurveillance: b.BlockSurveillance,
AllowedHosts: b.AllowedHosts, AllowedHosts: b.AllowedHosts,
AddBlockedHosts: b.AddBlockedHosts, AddBlockedHosts: b.AddBlockedHosts,
AddBlockedIPs: b.AddBlockedIPs, AddBlockedIPs: netipAddressesToNetaddrIPs(b.AddBlockedIPs),
AddBlockedIPPrefixes: b.AddBlockedIPPrefixes, AddBlockedIPPrefixes: netipPrefixesToNetaddrIPPrefixes(b.AddBlockedIPPrefixes),
} }, nil
} }
func (b DNSBlacklist) String() string { func (b DNSBlacklist) String() string {
@@ -102,94 +107,32 @@ func (b DNSBlacklist) toLinesNode() (node *gotree.Node) {
node.Appendf("Block surveillance: %s", gosettings.BoolToYesNo(b.BlockSurveillance)) node.Appendf("Block surveillance: %s", gosettings.BoolToYesNo(b.BlockSurveillance))
if len(b.AllowedHosts) > 0 { if len(b.AllowedHosts) > 0 {
allowedHostsNode := node.Append("Allowed hosts:") allowedHostsNode := node.Appendf("Allowed hosts:")
for _, host := range b.AllowedHosts { for _, host := range b.AllowedHosts {
allowedHostsNode.Append(host) allowedHostsNode.Appendf(host)
} }
} }
if len(b.AddBlockedHosts) > 0 { if len(b.AddBlockedHosts) > 0 {
blockedHostsNode := node.Append("Blocked hosts:") blockedHostsNode := node.Appendf("Blocked hosts:")
for _, host := range b.AddBlockedHosts { for _, host := range b.AddBlockedHosts {
blockedHostsNode.Append(host) blockedHostsNode.Appendf(host)
} }
} }
if len(b.AddBlockedIPs) > 0 { if len(b.AddBlockedIPs) > 0 {
blockedIPsNode := node.Append("Blocked IP addresses:") blockedIPsNode := node.Appendf("Blocked IP addresses:")
for _, ip := range b.AddBlockedIPs { for _, ip := range b.AddBlockedIPs {
blockedIPsNode.Append(ip.String()) blockedIPsNode.Appendf(ip.String())
} }
} }
if len(b.AddBlockedIPPrefixes) > 0 { if len(b.AddBlockedIPPrefixes) > 0 {
blockedIPPrefixesNode := node.Append("Blocked IP networks:") blockedIPPrefixesNode := node.Appendf("Blocked IP networks:")
for _, ipNetwork := range b.AddBlockedIPPrefixes { for _, ipNetwork := range b.AddBlockedIPPrefixes {
blockedIPPrefixesNode.Append(ipNetwork.String()) blockedIPPrefixesNode.Appendf(ipNetwork.String())
} }
} }
return node return node
} }
func (b *DNSBlacklist) read(r *reader.Reader) (err error) {
b.BlockMalicious, err = r.BoolPtr("BLOCK_MALICIOUS")
if err != nil {
return err
}
b.BlockSurveillance, err = r.BoolPtr("BLOCK_SURVEILLANCE",
reader.RetroKeys("BLOCK_NSA"))
if err != nil {
return err
}
b.BlockAds, err = r.BoolPtr("BLOCK_ADS")
if err != nil {
return err
}
b.AddBlockedIPs, b.AddBlockedIPPrefixes,
err = readDoTPrivateAddresses(r) // TODO v4 split in 2
if err != nil {
return err
}
b.AllowedHosts = r.CSV("UNBLOCK") // TODO v4 change name
return nil
}
var ErrPrivateAddressNotValid = errors.New("private address is not a valid IP or CIDR range")
func readDoTPrivateAddresses(reader *reader.Reader) (ips []netip.Addr,
ipPrefixes []netip.Prefix, err error,
) {
privateAddresses := reader.CSV("DOT_PRIVATE_ADDRESS")
if len(privateAddresses) == 0 {
return nil, nil, nil
}
ips = make([]netip.Addr, 0, len(privateAddresses))
ipPrefixes = make([]netip.Prefix, 0, len(privateAddresses))
for _, privateAddress := range privateAddresses {
ip, err := netip.ParseAddr(privateAddress)
if err == nil {
ips = append(ips, ip)
continue
}
ipPrefix, err := netip.ParsePrefix(privateAddress)
if err == nil {
ipPrefixes = append(ipPrefixes, ipPrefix)
continue
}
return nil, nil, fmt.Errorf(
"environment variable DOT_PRIVATE_ADDRESS: %w: %s",
ErrPrivateAddressNotValid, privateAddress)
}
return ips, ipPrefixes, nil
}

View File

@@ -3,12 +3,9 @@ package settings
import ( import (
"errors" "errors"
"fmt" "fmt"
"net/netip"
"time" "time"
"github.com/qdm12/dns/v2/pkg/provider"
"github.com/qdm12/gosettings" "github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/reader"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
) )
@@ -18,24 +15,22 @@ type DoT struct {
// and used. It defaults to true, and cannot be nil // and used. It defaults to true, and cannot be nil
// in the internal state. // in the internal state.
Enabled *bool Enabled *bool
// UpdatePeriod is the period to update DNS block lists. // UpdatePeriod is the period to update DNS block
// lists and cryptographic files for DNSSEC validation.
// It can be set to 0 to disable the update. // It can be set to 0 to disable the update.
// It defaults to 24h and cannot be nil in // It defaults to 24h and cannot be nil in
// the internal state. // the internal state.
UpdatePeriod *time.Duration UpdatePeriod *time.Duration
// Providers is a list of DNS over TLS providers // Unbound contains settings to configure Unbound.
Providers []string `json:"providers"` Unbound Unbound
// Caching is true if the DoT server should cache
// DNS responses.
Caching *bool `json:"caching"`
// IPv6 is true if the DoT server should connect over IPv6.
IPv6 *bool `json:"ipv6"`
// Blacklist contains settings to configure the filter // Blacklist contains settings to configure the filter
// block lists. // block lists.
Blacklist DNSBlacklist Blacklist DNSBlacklist
} }
var ErrDoTUpdatePeriodTooShort = errors.New("update period is too short") var (
ErrDoTUpdatePeriodTooShort = errors.New("update period is too short")
)
func (d DoT) validate() (err error) { func (d DoT) validate() (err error) {
const minUpdatePeriod = 30 * time.Second const minUpdatePeriod = 30 * time.Second
@@ -44,13 +39,10 @@ func (d DoT) validate() (err error) {
ErrDoTUpdatePeriodTooShort, *d.UpdatePeriod, minUpdatePeriod) ErrDoTUpdatePeriodTooShort, *d.UpdatePeriod, minUpdatePeriod)
} }
providers := provider.NewProviders() err = d.Unbound.validate()
for _, providerName := range d.Providers {
_, err := providers.Get(providerName)
if err != nil { if err != nil {
return err return err
} }
}
err = d.Blacklist.validate() err = d.Blacklist.validate()
if err != nil { if err != nil {
@@ -64,22 +56,27 @@ func (d *DoT) copy() (copied DoT) {
return DoT{ return DoT{
Enabled: gosettings.CopyPointer(d.Enabled), Enabled: gosettings.CopyPointer(d.Enabled),
UpdatePeriod: gosettings.CopyPointer(d.UpdatePeriod), UpdatePeriod: gosettings.CopyPointer(d.UpdatePeriod),
Providers: gosettings.CopySlice(d.Providers), Unbound: d.Unbound.copy(),
Caching: gosettings.CopyPointer(d.Caching),
IPv6: gosettings.CopyPointer(d.IPv6),
Blacklist: d.Blacklist.copy(), Blacklist: d.Blacklist.copy(),
} }
} }
// mergeWith merges the other settings into any
// unset field of the receiver settings object.
func (d *DoT) mergeWith(other DoT) {
d.Enabled = gosettings.MergeWithPointer(d.Enabled, other.Enabled)
d.UpdatePeriod = gosettings.MergeWithPointer(d.UpdatePeriod, other.UpdatePeriod)
d.Unbound.mergeWith(other.Unbound)
d.Blacklist.mergeWith(other.Blacklist)
}
// 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 (d *DoT) overrideWith(other DoT) { func (d *DoT) overrideWith(other DoT) {
d.Enabled = gosettings.OverrideWithPointer(d.Enabled, other.Enabled) d.Enabled = gosettings.OverrideWithPointer(d.Enabled, other.Enabled)
d.UpdatePeriod = gosettings.OverrideWithPointer(d.UpdatePeriod, other.UpdatePeriod) d.UpdatePeriod = gosettings.OverrideWithPointer(d.UpdatePeriod, other.UpdatePeriod)
d.Providers = gosettings.OverrideWithSlice(d.Providers, other.Providers) d.Unbound.overrideWith(other.Unbound)
d.Caching = gosettings.OverrideWithPointer(d.Caching, other.Caching)
d.IPv6 = gosettings.OverrideWithPointer(d.IPv6, other.IPv6)
d.Blacklist.overrideWith(other.Blacklist) d.Blacklist.overrideWith(other.Blacklist)
} }
@@ -87,26 +84,10 @@ func (d *DoT) setDefaults() {
d.Enabled = gosettings.DefaultPointer(d.Enabled, true) d.Enabled = gosettings.DefaultPointer(d.Enabled, true)
const defaultUpdatePeriod = 24 * time.Hour const defaultUpdatePeriod = 24 * time.Hour
d.UpdatePeriod = gosettings.DefaultPointer(d.UpdatePeriod, defaultUpdatePeriod) d.UpdatePeriod = gosettings.DefaultPointer(d.UpdatePeriod, defaultUpdatePeriod)
d.Providers = gosettings.DefaultSlice(d.Providers, []string{ d.Unbound.setDefaults()
provider.Cloudflare().Name,
})
d.Caching = gosettings.DefaultPointer(d.Caching, true)
d.IPv6 = gosettings.DefaultPointer(d.IPv6, false)
d.Blacklist.setDefaults() d.Blacklist.setDefaults()
} }
func (d DoT) GetFirstPlaintextIPv4() (ipv4 netip.Addr) {
providers := provider.NewProviders()
provider, err := providers.Get(d.Providers[0])
if err != nil {
// Settings should be validated before calling this function,
// so an error happening here is a programming error.
panic(err)
}
return provider.DoT.IPv4[0].Addr()
}
func (d DoT) String() string { func (d DoT) String() string {
return d.toLinesNode().String() return d.toLinesNode().String()
} }
@@ -125,46 +106,8 @@ func (d DoT) toLinesNode() (node *gotree.Node) {
} }
node.Appendf("Update period: %s", update) node.Appendf("Update period: %s", update)
upstreamResolvers := node.Append("Upstream resolvers:") node.AppendNode(d.Unbound.toLinesNode())
for _, provider := range d.Providers {
upstreamResolvers.Append(provider)
}
node.Appendf("Caching: %s", gosettings.BoolToYesNo(d.Caching))
node.Appendf("IPv6: %s", gosettings.BoolToYesNo(d.IPv6))
node.AppendNode(d.Blacklist.toLinesNode()) node.AppendNode(d.Blacklist.toLinesNode())
return node return node
} }
func (d *DoT) read(reader *reader.Reader) (err error) {
d.Enabled, err = reader.BoolPtr("DOT")
if err != nil {
return err
}
d.UpdatePeriod, err = reader.DurationPtr("DNS_UPDATE_PERIOD")
if err != nil {
return err
}
d.Providers = reader.CSV("DOT_PROVIDERS")
d.Caching, err = reader.BoolPtr("DOT_CACHING")
if err != nil {
return err
}
d.IPv6, err = reader.BoolPtr("DOT_IPV6")
if err != nil {
return err
}
err = d.Blacklist.read(reader)
if err != nil {
return err
}
return nil
}

View File

@@ -3,7 +3,6 @@ package settings
import "errors" import "errors"
var ( var (
ErrValueUnknown = errors.New("value is unknown")
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") ErrCategoryNotValid = errors.New("the category specified is not valid")
@@ -28,8 +27,7 @@ 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")
ErrPortForwardingUserEmpty = errors.New("port forwarding username is empty") ErrPublicIPPeriodTooShort = errors.New("public IP address check period is too short")
ErrPortForwardingPasswordEmpty = errors.New("port forwarding password is empty")
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")
@@ -51,6 +49,5 @@ var (
ErrWireguardPrivateKeyNotSet = errors.New("private key is not set") ErrWireguardPrivateKeyNotSet = errors.New("private key is not set")
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")
ErrWireguardKeepAliveNegative = errors.New("persistent keep alive interval is negative")
ErrWireguardImplementationNotValid = errors.New("implementation is not valid") ErrWireguardImplementationNotValid = errors.New("implementation is not valid")
) )

View File

@@ -5,7 +5,6 @@ import (
"net/netip" "net/netip"
"github.com/qdm12/gosettings" "github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/reader"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
) )
@@ -55,6 +54,18 @@ func (f *Firewall) copy() (copied Firewall) {
} }
} }
// mergeWith merges the other settings into any
// unset field of the receiver settings object.
// It merges values of slices together, even if they
// are set in the receiver settings.
func (f *Firewall) mergeWith(other Firewall) {
f.VPNInputPorts = gosettings.MergeWithSlice(f.VPNInputPorts, other.VPNInputPorts)
f.InputPorts = gosettings.MergeWithSlice(f.InputPorts, other.InputPorts)
f.OutboundSubnets = gosettings.MergeWithSlice(f.OutboundSubnets, other.OutboundSubnets)
f.Enabled = gosettings.MergeWithPointer(f.Enabled, other.Enabled)
f.Debug = gosettings.MergeWithPointer(f.Debug, other.Debug)
}
// overrideWith overrides fields of the receiver // 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.
@@ -104,39 +115,10 @@ 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)
} }
} }
return node return node
} }
func (f *Firewall) read(r *reader.Reader) (err error) {
f.VPNInputPorts, err = r.CSVUint16("FIREWALL_VPN_INPUT_PORTS")
if err != nil {
return err
}
f.InputPorts, err = r.CSVUint16("FIREWALL_INPUT_PORTS")
if err != nil {
return err
}
f.OutboundSubnets, err = r.CSVNetipPrefixes(
"FIREWALL_OUTBOUND_SUBNETS", reader.RetroKeys("EXTRA_SUBNETS"))
if err != nil {
return err
}
f.Enabled, err = r.BoolPtr("FIREWALL_ENABLED_DISABLING_IT_SHOOTS_YOU_IN_YOUR_FOOT")
if err != nil {
return err
}
f.Debug, err = r.BoolPtr("FIREWALL_DEBUG")
if err != nil {
return err
}
return nil
}

View File

@@ -59,6 +59,7 @@ func Test_Firewall_validate(t *testing.T) {
} }
for name, testCase := range testCases { for name, testCase := range testCases {
testCase := testCase
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
t.Parallel() t.Parallel()

View File

@@ -6,9 +6,8 @@ import (
"time" "time"
"github.com/qdm12/gosettings" "github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/reader"
"github.com/qdm12/gosettings/validate"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
"github.com/qdm12/govalid/address"
) )
// Health contains settings for the healthcheck and health server. // Health contains settings for the healthcheck and health server.
@@ -37,7 +36,9 @@ type Health struct {
} }
func (h Health) Validate() (err error) { func (h Health) Validate() (err error) {
err = validate.ListeningAddress(h.ServerAddress, os.Getuid()) uid := os.Getuid()
err = address.Validate(h.ServerAddress,
address.OptionListening(uid))
if err != nil { if err != nil {
return fmt.Errorf("server listening address is not valid: %w", err) return fmt.Errorf("server listening address is not valid: %w", err)
} }
@@ -61,27 +62,38 @@ func (h *Health) copy() (copied Health) {
} }
} }
// MergeWith merges the other settings into any
// unset field of the receiver settings object.
func (h *Health) MergeWith(other Health) {
h.ServerAddress = gosettings.MergeWithString(h.ServerAddress, other.ServerAddress)
h.ReadHeaderTimeout = gosettings.MergeWithNumber(h.ReadHeaderTimeout, other.ReadHeaderTimeout)
h.ReadTimeout = gosettings.MergeWithNumber(h.ReadTimeout, other.ReadTimeout)
h.TargetAddress = gosettings.MergeWithString(h.TargetAddress, other.TargetAddress)
h.SuccessWait = gosettings.MergeWithNumber(h.SuccessWait, other.SuccessWait)
h.VPN.mergeWith(other.VPN)
}
// 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 *Health) OverrideWith(other Health) { func (h *Health) OverrideWith(other Health) {
h.ServerAddress = gosettings.OverrideWithComparable(h.ServerAddress, other.ServerAddress) h.ServerAddress = gosettings.OverrideWithString(h.ServerAddress, other.ServerAddress)
h.ReadHeaderTimeout = gosettings.OverrideWithComparable(h.ReadHeaderTimeout, other.ReadHeaderTimeout) h.ReadHeaderTimeout = gosettings.OverrideWithNumber(h.ReadHeaderTimeout, other.ReadHeaderTimeout)
h.ReadTimeout = gosettings.OverrideWithComparable(h.ReadTimeout, other.ReadTimeout) h.ReadTimeout = gosettings.OverrideWithNumber(h.ReadTimeout, other.ReadTimeout)
h.TargetAddress = gosettings.OverrideWithComparable(h.TargetAddress, other.TargetAddress) h.TargetAddress = gosettings.OverrideWithString(h.TargetAddress, other.TargetAddress)
h.SuccessWait = gosettings.OverrideWithComparable(h.SuccessWait, other.SuccessWait) 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.DefaultComparable(h.ServerAddress, "127.0.0.1:9999") h.ServerAddress = gosettings.DefaultString(h.ServerAddress, "127.0.0.1:9999")
const defaultReadHeaderTimeout = 100 * time.Millisecond const defaultReadHeaderTimeout = 100 * time.Millisecond
h.ReadHeaderTimeout = gosettings.DefaultComparable(h.ReadHeaderTimeout, defaultReadHeaderTimeout) h.ReadHeaderTimeout = gosettings.DefaultNumber(h.ReadHeaderTimeout, defaultReadHeaderTimeout)
const defaultReadTimeout = 500 * time.Millisecond const defaultReadTimeout = 500 * time.Millisecond
h.ReadTimeout = gosettings.DefaultComparable(h.ReadTimeout, defaultReadTimeout) h.ReadTimeout = gosettings.DefaultNumber(h.ReadTimeout, defaultReadTimeout)
h.TargetAddress = gosettings.DefaultComparable(h.TargetAddress, "cloudflare.com:443") h.TargetAddress = gosettings.DefaultString(h.TargetAddress, "cloudflare.com:443")
const defaultSuccessWait = 5 * time.Second const defaultSuccessWait = 5 * time.Second
h.SuccessWait = gosettings.DefaultComparable(h.SuccessWait, defaultSuccessWait) h.SuccessWait = gosettings.DefaultNumber(h.SuccessWait, defaultSuccessWait)
h.VPN.setDefaults() h.VPN.setDefaults()
} }
@@ -99,21 +111,3 @@ func (h Health) toLinesNode() (node *gotree.Node) {
node.AppendNode(h.VPN.toLinesNode("VPN")) node.AppendNode(h.VPN.toLinesNode("VPN"))
return node return node
} }
func (h *Health) Read(r *reader.Reader) (err error) {
h.ServerAddress = r.String("HEALTH_SERVER_ADDRESS")
h.TargetAddress = r.String("HEALTH_TARGET_ADDRESS",
reader.RetroKeys("HEALTH_ADDRESS_TO_PING"))
h.SuccessWait, err = r.Duration("HEALTH_SUCCESS_WAIT_DURATION")
if err != nil {
return err
}
err = h.VPN.read(r)
if err != nil {
return fmt.Errorf("VPN health settings: %w", err)
}
return nil
}

View File

@@ -4,7 +4,6 @@ import (
"time" "time"
"github.com/qdm12/gosettings" "github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/reader"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
) )
@@ -24,6 +23,8 @@ func (h HealthyWait) validate() (err error) {
return nil return nil
} }
// mergeWith merges the other settings into any
// 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: gosettings.CopyPointer(h.Initial),
@@ -31,6 +32,13 @@ func (h *HealthyWait) copy() (copied HealthyWait) {
} }
} }
// mergeWith merges the other settings into any
// unset field of the receiver settings object.
func (h *HealthyWait) mergeWith(other HealthyWait) {
h.Initial = gosettings.MergeWithPointer(h.Initial, other.Initial)
h.Addition = gosettings.MergeWithPointer(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.
@@ -56,21 +64,3 @@ func (h HealthyWait) toLinesNode(kind string) (node *gotree.Node) {
node.Appendf("Additional duration: %s", *h.Addition) node.Appendf("Additional duration: %s", *h.Addition)
return node return node
} }
func (h *HealthyWait) read(r *reader.Reader) (err error) {
h.Initial, err = r.DurationPtr(
"HEALTH_VPN_DURATION_INITIAL",
reader.RetroKeys("HEALTH_OPENVPN_DURATION_INITIAL"))
if err != nil {
return err
}
h.Addition, err = r.DurationPtr(
"HEALTH_VPN_DURATION_ADDITION",
reader.RetroKeys("HEALTH_OPENVPN_DURATION_ADDITION"))
if err != nil {
return err
}
return nil
}

View File

@@ -1,5 +0,0 @@
package settings
func ptrTo[T any](value T) *T {
return &value
}

View File

@@ -0,0 +1,8 @@
package helpers
func TCPPtrToString(tcp *bool) string {
if *tcp {
return "TCP"
}
return "UDP"
}

View File

@@ -1,30 +1,4 @@
package settings package settings
import gomock "github.com/golang/mock/gomock" func boolPtr(b bool) *bool { return &b }
func uint8Ptr(n uint8) *uint8 { return &n }
type sourceKeyValue struct {
key string
value string
}
func newMockSource(ctrl *gomock.Controller, keyValues []sourceKeyValue) *MockSource {
source := NewMockSource(ctrl)
var previousCall *gomock.Call
for _, keyValue := range keyValues {
transformedKey := keyValue.key
keyTransformCall := source.EXPECT().KeyTransform(keyValue.key).Return(transformedKey)
if previousCall != nil {
keyTransformCall.After(previousCall)
}
isSet := keyValue.value != ""
previousCall = source.EXPECT().Get(transformedKey).
Return(keyValue.value, isSet).After(keyTransformCall)
if isSet {
previousCall = source.EXPECT().KeyTransform(keyValue.key).
Return(transformedKey).After(previousCall)
previousCall = source.EXPECT().String().
Return("mock source").After(previousCall)
}
}
return source
}

View File

@@ -3,13 +3,11 @@ package settings
import ( import (
"fmt" "fmt"
"os" "os"
"strings"
"time" "time"
"github.com/qdm12/gosettings" "github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/reader"
"github.com/qdm12/gosettings/validate"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
"github.com/qdm12/govalid/address"
) )
// HTTPProxy contains settings to configure the HTTP proxy. // HTTPProxy contains settings to configure the HTTP proxy.
@@ -46,7 +44,9 @@ type HTTPProxy struct {
func (h HTTPProxy) validate() (err error) { func (h HTTPProxy) validate() (err error) {
// Do not validate user and password // Do not validate user and password
err = validate.ListeningAddress(h.ListeningAddress, os.Getuid())
uid := os.Getuid()
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)
} }
@@ -67,31 +67,44 @@ func (h *HTTPProxy) copy() (copied HTTPProxy) {
} }
} }
// mergeWith merges the other settings into any
// unset field of the receiver settings object.
func (h *HTTPProxy) mergeWith(other HTTPProxy) {
h.User = gosettings.MergeWithPointer(h.User, other.User)
h.Password = gosettings.MergeWithPointer(h.Password, other.Password)
h.ListeningAddress = gosettings.MergeWithString(h.ListeningAddress, other.ListeningAddress)
h.Enabled = gosettings.MergeWithPointer(h.Enabled, other.Enabled)
h.Stealth = gosettings.MergeWithPointer(h.Stealth, other.Stealth)
h.Log = gosettings.MergeWithPointer(h.Log, other.Log)
h.ReadHeaderTimeout = gosettings.MergeWithNumber(h.ReadHeaderTimeout, other.ReadHeaderTimeout)
h.ReadTimeout = gosettings.MergeWithNumber(h.ReadTimeout, other.ReadTimeout)
}
// overrideWith overrides fields of the receiver // 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 = gosettings.OverrideWithPointer(h.User, other.User)
h.Password = gosettings.OverrideWithPointer(h.Password, other.Password) h.Password = gosettings.OverrideWithPointer(h.Password, other.Password)
h.ListeningAddress = gosettings.OverrideWithComparable(h.ListeningAddress, other.ListeningAddress) h.ListeningAddress = gosettings.OverrideWithString(h.ListeningAddress, other.ListeningAddress)
h.Enabled = gosettings.OverrideWithPointer(h.Enabled, other.Enabled) h.Enabled = gosettings.OverrideWithPointer(h.Enabled, other.Enabled)
h.Stealth = gosettings.OverrideWithPointer(h.Stealth, other.Stealth) h.Stealth = gosettings.OverrideWithPointer(h.Stealth, other.Stealth)
h.Log = gosettings.OverrideWithPointer(h.Log, other.Log) h.Log = gosettings.OverrideWithPointer(h.Log, other.Log)
h.ReadHeaderTimeout = gosettings.OverrideWithComparable(h.ReadHeaderTimeout, other.ReadHeaderTimeout) h.ReadHeaderTimeout = gosettings.OverrideWithNumber(h.ReadHeaderTimeout, other.ReadHeaderTimeout)
h.ReadTimeout = gosettings.OverrideWithComparable(h.ReadTimeout, other.ReadTimeout) h.ReadTimeout = gosettings.OverrideWithNumber(h.ReadTimeout, other.ReadTimeout)
} }
func (h *HTTPProxy) setDefaults() { func (h *HTTPProxy) setDefaults() {
h.User = gosettings.DefaultPointer(h.User, "") h.User = gosettings.DefaultPointer(h.User, "")
h.Password = gosettings.DefaultPointer(h.Password, "") h.Password = gosettings.DefaultPointer(h.Password, "")
h.ListeningAddress = gosettings.DefaultComparable(h.ListeningAddress, ":8888") h.ListeningAddress = gosettings.DefaultString(h.ListeningAddress, ":8888")
h.Enabled = gosettings.DefaultPointer(h.Enabled, false) h.Enabled = gosettings.DefaultPointer(h.Enabled, false)
h.Stealth = gosettings.DefaultPointer(h.Stealth, false) h.Stealth = gosettings.DefaultPointer(h.Stealth, false)
h.Log = gosettings.DefaultPointer(h.Log, false) h.Log = gosettings.DefaultPointer(h.Log, false)
const defaultReadHeaderTimeout = time.Second const defaultReadHeaderTimeout = time.Second
h.ReadHeaderTimeout = gosettings.DefaultComparable(h.ReadHeaderTimeout, defaultReadHeaderTimeout) h.ReadHeaderTimeout = gosettings.DefaultNumber(h.ReadHeaderTimeout, defaultReadHeaderTimeout)
const defaultReadTimeout = 3 * time.Second const defaultReadTimeout = 3 * time.Second
h.ReadTimeout = gosettings.DefaultComparable(h.ReadTimeout, defaultReadTimeout) h.ReadTimeout = gosettings.DefaultNumber(h.ReadTimeout, defaultReadTimeout)
} }
func (h HTTPProxy) String() string { func (h HTTPProxy) String() string {
@@ -115,68 +128,3 @@ func (h HTTPProxy) toLinesNode() (node *gotree.Node) {
return node return node
} }
func (h *HTTPProxy) read(r *reader.Reader) (err error) {
h.User = r.Get("HTTPPROXY_USER",
reader.RetroKeys("PROXY_USER", "TINYPROXY_USER"),
reader.ForceLowercase(false))
h.Password = r.Get("HTTPPROXY_PASSWORD",
reader.RetroKeys("PROXY_PASSWORD", "TINYPROXY_PASSWORD"),
reader.ForceLowercase(false))
h.ListeningAddress, err = readHTTProxyListeningAddress(r)
if err != nil {
return err
}
h.Enabled, err = r.BoolPtr("HTTPPROXY", reader.RetroKeys("PROXY", "TINYPROXY"))
if err != nil {
return err
}
h.Stealth, err = r.BoolPtr("HTTPPROXY_STEALTH")
if err != nil {
return err
}
h.Log, err = readHTTProxyLog(r)
if err != nil {
return err
}
return nil
}
func readHTTProxyListeningAddress(r *reader.Reader) (listeningAddress string, err error) {
// Retro-compatible keys using a port only
port, err := r.Uint16Ptr("",
reader.RetroKeys("HTTPPROXY_PORT", "TINYPROXY_PORT", "PROXY_PORT"),
reader.IsRetro("HTTPPROXY_LISTENING_ADDRESS"))
if err != nil {
return "", err
} else if port != nil {
return fmt.Sprintf(":%d", *port), nil
}
const currentKey = "HTTPPROXY_LISTENING_ADDRESS"
return r.String(currentKey), nil
}
func readHTTProxyLog(r *reader.Reader) (enabled *bool, err error) {
const currentKey = "HTTPPROXY_LOG"
// Retro-compatible keys using different boolean verbs
value := r.String("",
reader.RetroKeys("PROXY_LOG", "TINYPROXY_LOG"),
reader.IsRetro(currentKey))
switch strings.ToLower(value) {
case "":
return r.BoolPtr(currentKey)
case "on", "info", "connect", "notice":
return ptrTo(true), nil
case "disabled", "no", "off":
return ptrTo(false), nil
default:
return nil, fmt.Errorf("HTTP retro-compatible proxy log setting: %w: %s",
ErrValueUnknown, value)
}
}

View File

@@ -1,5 +0,0 @@
package settings
type Warner interface {
Warn(message string)
}

View File

@@ -1,10 +1,7 @@
package settings package settings
import ( import (
"fmt"
"github.com/qdm12/gosettings" "github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/reader"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
"github.com/qdm12/log" "github.com/qdm12/log"
) )
@@ -12,33 +9,35 @@ import (
// 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 empty in the internal state. // It cannot be nil in the internal state.
Level string Level *log.Level
} }
func (l Log) validate() (err error) { func (l Log) validate() (err error) {
_, err = log.ParseLevel(l.Level)
if err != nil {
return fmt.Errorf("level: %w", err)
}
return nil return nil
} }
func (l *Log) copy() (copied Log) { func (l *Log) copy() (copied Log) {
return Log{ return Log{
Level: l.Level, Level: gosettings.CopyPointer(l.Level),
} }
} }
// mergeWith merges the other settings into any
// unset field of the receiver settings object.
func (l *Log) mergeWith(other Log) {
l.Level = gosettings.MergeWithPointer(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.OverrideWithComparable(l.Level, other.Level) l.Level = gosettings.OverrideWithPointer(l.Level, other.Level)
} }
func (l *Log) setDefaults() { func (l *Log) setDefaults() {
l.Level = gosettings.DefaultComparable(l.Level, log.LevelInfo.String()) l.Level = gosettings.DefaultPointer(l.Level, log.LevelInfo)
} }
func (l Log) String() string { func (l Log) String() string {
@@ -47,11 +46,6 @@ func (l Log) String() string {
func (l Log) toLinesNode() (node *gotree.Node) { func (l Log) toLinesNode() (node *gotree.Node) {
node = gotree.New("Log settings:") node = gotree.New("Log settings:")
node.Appendf("Log level: %s", l.Level) node.Appendf("Log level: %s", l.Level.String())
return node return node
} }
func (l *Log) read(r *reader.Reader) (err error) {
l.Level = r.String("LOG_LEVEL")
return nil
}

View File

@@ -1,4 +0,0 @@
package settings
//go:generate mockgen -destination=mocks_test.go -package=$GOPACKAGE . Warner
//go:generate mockgen -destination=mocks_reader_test.go -package=$GOPACKAGE github.com/qdm12/gosettings/reader Source

View File

@@ -1,77 +0,0 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/qdm12/gosettings/reader (interfaces: Source)
// Package settings is a generated GoMock package.
package settings
import (
reflect "reflect"
gomock "github.com/golang/mock/gomock"
)
// MockSource is a mock of Source interface.
type MockSource struct {
ctrl *gomock.Controller
recorder *MockSourceMockRecorder
}
// MockSourceMockRecorder is the mock recorder for MockSource.
type MockSourceMockRecorder struct {
mock *MockSource
}
// NewMockSource creates a new mock instance.
func NewMockSource(ctrl *gomock.Controller) *MockSource {
mock := &MockSource{ctrl: ctrl}
mock.recorder = &MockSourceMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockSource) EXPECT() *MockSourceMockRecorder {
return m.recorder
}
// Get mocks base method.
func (m *MockSource) Get(arg0 string) (string, bool) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Get", arg0)
ret0, _ := ret[0].(string)
ret1, _ := ret[1].(bool)
return ret0, ret1
}
// Get indicates an expected call of Get.
func (mr *MockSourceMockRecorder) Get(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockSource)(nil).Get), arg0)
}
// KeyTransform mocks base method.
func (m *MockSource) KeyTransform(arg0 string) string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "KeyTransform", arg0)
ret0, _ := ret[0].(string)
return ret0
}
// KeyTransform indicates an expected call of KeyTransform.
func (mr *MockSourceMockRecorder) KeyTransform(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "KeyTransform", reflect.TypeOf((*MockSource)(nil).KeyTransform), arg0)
}
// String mocks base method.
func (m *MockSource) String() string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "String")
ret0, _ := ret[0].(string)
return ret0
}
// String indicates an expected call of String.
func (mr *MockSourceMockRecorder) String() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "String", reflect.TypeOf((*MockSource)(nil).String))
}

View File

@@ -1,46 +0,0 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/qdm12/gluetun/internal/configuration/settings (interfaces: Warner)
// Package settings is a generated GoMock package.
package settings
import (
reflect "reflect"
gomock "github.com/golang/mock/gomock"
)
// MockWarner is a mock of Warner interface.
type MockWarner struct {
ctrl *gomock.Controller
recorder *MockWarnerMockRecorder
}
// MockWarnerMockRecorder is the mock recorder for MockWarner.
type MockWarnerMockRecorder struct {
mock *MockWarner
}
// NewMockWarner creates a new mock instance.
func NewMockWarner(ctrl *gomock.Controller) *MockWarner {
mock := &MockWarner{ctrl: ctrl}
mock.recorder = &MockWarnerMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockWarner) EXPECT() *MockWarnerMockRecorder {
return m.recorder
}
// Warn mocks base method.
func (m *MockWarner) Warn(arg0 string) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "Warn", arg0)
}
// Warn indicates an expected call of Warn.
func (mr *MockWarnerMockRecorder) Warn(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Warn", reflect.TypeOf((*MockWarner)(nil).Warn), arg0)
}

View File

@@ -0,0 +1,36 @@
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
}

View File

@@ -4,8 +4,7 @@ package settings
// and SERVER_REGIONS is now the continent field for servers. // and SERVER_REGIONS is now the continent field for servers.
// TODO v4 remove. // TODO v4 remove.
func nordvpnRetroRegion(selection ServerSelection, validRegions, validCountries []string) ( func nordvpnRetroRegion(selection ServerSelection, validRegions, validCountries []string) (
updatedSelection ServerSelection, updatedSelection ServerSelection) {
) {
validRegionsMap := stringSliceToMap(validRegions) validRegionsMap := stringSliceToMap(validRegions)
validCountriesMap := stringSliceToMap(validCountries) validCountriesMap := stringSliceToMap(validCountries)

View File

@@ -4,14 +4,12 @@ import (
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"regexp" "regexp"
"strings"
"github.com/qdm12/gluetun/internal/constants/openvpn" "github.com/qdm12/gluetun/internal/constants/openvpn"
"github.com/qdm12/gluetun/internal/constants/providers" "github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/openvpn/extract" "github.com/qdm12/gluetun/internal/openvpn/extract"
"github.com/qdm12/gluetun/internal/provider/privateinternetaccess/presets" "github.com/qdm12/gluetun/internal/provider/privateinternetaccess/presets"
"github.com/qdm12/gosettings" "github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/reader"
"github.com/qdm12/gosettings/validate" "github.com/qdm12/gosettings/validate"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
) )
@@ -155,8 +153,7 @@ func (o OpenVPN) validate(vpnProvider string) (err error) {
} }
func validateOpenVPNConfigFilepath(isCustom bool, func validateOpenVPNConfigFilepath(isCustom bool,
confFile string, confFile string) (err error) {
) (err error) {
if !isCustom { if !isCustom {
return nil return nil
} }
@@ -180,8 +177,7 @@ func validateOpenVPNConfigFilepath(isCustom bool,
} }
func validateOpenVPNClientCertificate(vpnProvider, func validateOpenVPNClientCertificate(vpnProvider,
clientCert string, clientCert string) (err error) {
) (err error) {
switch vpnProvider { switch vpnProvider {
case case
providers.Airvpn, providers.Airvpn,
@@ -228,8 +224,7 @@ func validateOpenVPNClientKey(vpnProvider, clientKey string) (err error) {
} }
func validateOpenVPNEncryptedKey(vpnProvider, func validateOpenVPNEncryptedKey(vpnProvider,
encryptedPrivateKey string, encryptedPrivateKey string) (err error) {
) (err error) {
if vpnProvider == providers.VPNSecure && encryptedPrivateKey == "" { if vpnProvider == providers.VPNSecure && encryptedPrivateKey == "" {
return fmt.Errorf("%w", ErrMissingValue) return fmt.Errorf("%w", ErrMissingValue)
} }
@@ -266,11 +261,32 @@ func (o *OpenVPN) copy() (copied OpenVPN) {
} }
} }
// mergeWith merges the other settings into any
// unset field of the receiver settings object.
func (o *OpenVPN) mergeWith(other OpenVPN) {
o.Version = gosettings.MergeWithString(o.Version, other.Version)
o.User = gosettings.MergeWithPointer(o.User, other.User)
o.Password = gosettings.MergeWithPointer(o.Password, other.Password)
o.ConfFile = gosettings.MergeWithPointer(o.ConfFile, other.ConfFile)
o.Ciphers = gosettings.MergeWithSlice(o.Ciphers, other.Ciphers)
o.Auth = gosettings.MergeWithPointer(o.Auth, other.Auth)
o.Cert = gosettings.MergeWithPointer(o.Cert, other.Cert)
o.Key = gosettings.MergeWithPointer(o.Key, other.Key)
o.EncryptedKey = gosettings.MergeWithPointer(o.EncryptedKey, other.EncryptedKey)
o.KeyPassphrase = gosettings.MergeWithPointer(o.KeyPassphrase, other.KeyPassphrase)
o.PIAEncPreset = gosettings.MergeWithPointer(o.PIAEncPreset, other.PIAEncPreset)
o.MSSFix = gosettings.MergeWithPointer(o.MSSFix, other.MSSFix)
o.Interface = gosettings.MergeWithString(o.Interface, other.Interface)
o.ProcessUser = gosettings.MergeWithString(o.ProcessUser, other.ProcessUser)
o.Verbosity = gosettings.MergeWithPointer(o.Verbosity, other.Verbosity)
o.Flags = gosettings.MergeWithSlice(o.Flags, other.Flags)
}
// overrideWith overrides fields of the receiver // 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.OverrideWithComparable(o.Version, other.Version) o.Version = gosettings.OverrideWithString(o.Version, other.Version)
o.User = gosettings.OverrideWithPointer(o.User, other.User) o.User = gosettings.OverrideWithPointer(o.User, other.User)
o.Password = gosettings.OverrideWithPointer(o.Password, other.Password) o.Password = gosettings.OverrideWithPointer(o.Password, other.Password)
o.ConfFile = gosettings.OverrideWithPointer(o.ConfFile, other.ConfFile) o.ConfFile = gosettings.OverrideWithPointer(o.ConfFile, other.ConfFile)
@@ -282,14 +298,14 @@ func (o *OpenVPN) overrideWith(other OpenVPN) {
o.KeyPassphrase = gosettings.OverrideWithPointer(o.KeyPassphrase, other.KeyPassphrase) o.KeyPassphrase = gosettings.OverrideWithPointer(o.KeyPassphrase, other.KeyPassphrase)
o.PIAEncPreset = gosettings.OverrideWithPointer(o.PIAEncPreset, other.PIAEncPreset) o.PIAEncPreset = gosettings.OverrideWithPointer(o.PIAEncPreset, other.PIAEncPreset)
o.MSSFix = gosettings.OverrideWithPointer(o.MSSFix, other.MSSFix) o.MSSFix = gosettings.OverrideWithPointer(o.MSSFix, other.MSSFix)
o.Interface = gosettings.OverrideWithComparable(o.Interface, other.Interface) o.Interface = gosettings.OverrideWithString(o.Interface, other.Interface)
o.ProcessUser = gosettings.OverrideWithComparable(o.ProcessUser, other.ProcessUser) o.ProcessUser = gosettings.OverrideWithString(o.ProcessUser, other.ProcessUser)
o.Verbosity = gosettings.OverrideWithPointer(o.Verbosity, other.Verbosity) o.Verbosity = gosettings.OverrideWithPointer(o.Verbosity, other.Verbosity)
o.Flags = gosettings.OverrideWithSlice(o.Flags, other.Flags) o.Flags = gosettings.OverrideWithSlice(o.Flags, other.Flags)
} }
func (o *OpenVPN) setDefaults(vpnProvider string) { func (o *OpenVPN) setDefaults(vpnProvider string) {
o.Version = gosettings.DefaultComparable(o.Version, openvpn.Openvpn26) o.Version = gosettings.DefaultString(o.Version, openvpn.Openvpn25)
o.User = gosettings.DefaultPointer(o.User, "") o.User = gosettings.DefaultPointer(o.User, "")
if vpnProvider == providers.Mullvad { if vpnProvider == providers.Mullvad {
o.Password = gosettings.DefaultPointer(o.Password, "m") o.Password = gosettings.DefaultPointer(o.Password, "m")
@@ -310,8 +326,8 @@ func (o *OpenVPN) setDefaults(vpnProvider string) {
} }
o.PIAEncPreset = gosettings.DefaultPointer(o.PIAEncPreset, defaultEncPreset) o.PIAEncPreset = gosettings.DefaultPointer(o.PIAEncPreset, defaultEncPreset)
o.MSSFix = gosettings.DefaultPointer(o.MSSFix, 0) o.MSSFix = gosettings.DefaultPointer(o.MSSFix, 0)
o.Interface = gosettings.DefaultComparable(o.Interface, "tun0") o.Interface = gosettings.DefaultString(o.Interface, "tun0")
o.ProcessUser = gosettings.DefaultComparable(o.ProcessUser, "root") o.ProcessUser = gosettings.DefaultString(o.ProcessUser, "root")
o.Verbosity = gosettings.DefaultPointer(o.Verbosity, 1) o.Verbosity = gosettings.DefaultPointer(o.Verbosity, 1)
} }
@@ -379,58 +395,3 @@ func (o OpenVPN) WithDefaults(provider string) OpenVPN {
o.setDefaults(provider) o.setDefaults(provider)
return o return o
} }
func (o *OpenVPN) read(r *reader.Reader) (err error) {
o.Version = r.String("OPENVPN_VERSION")
o.User = r.Get("OPENVPN_USER", reader.RetroKeys("USER"), reader.ForceLowercase(false))
o.Password = r.Get("OPENVPN_PASSWORD", reader.RetroKeys("PASSWORD"), reader.ForceLowercase(false))
o.ConfFile = r.Get("OPENVPN_CUSTOM_CONFIG", reader.ForceLowercase(false))
o.Ciphers = r.CSV("OPENVPN_CIPHERS", reader.RetroKeys("OPENVPN_CIPHER"))
o.Auth = r.Get("OPENVPN_AUTH")
o.Cert = r.Get("OPENVPN_CERT", reader.ForceLowercase(false))
o.Key = r.Get("OPENVPN_KEY", reader.ForceLowercase(false))
o.EncryptedKey = r.Get("OPENVPN_ENCRYPTED_KEY", reader.ForceLowercase(false))
o.KeyPassphrase = r.Get("OPENVPN_KEY_PASSPHRASE", reader.ForceLowercase(false))
o.PIAEncPreset = r.Get("PRIVATE_INTERNET_ACCESS_OPENVPN_ENCRYPTION_PRESET",
reader.RetroKeys("ENCRYPTION", "PIA_ENCRYPTION"))
o.MSSFix, err = r.Uint16Ptr("OPENVPN_MSSFIX")
if err != nil {
return err
}
o.Interface = r.String("VPN_INTERFACE",
reader.RetroKeys("OPENVPN_INTERFACE"), reader.ForceLowercase(false))
o.ProcessUser, err = readOpenVPNProcessUser(r)
if err != nil {
return err
}
o.Verbosity, err = r.IntPtr("OPENVPN_VERBOSITY")
if err != nil {
return err
}
flagsPtr := r.Get("OPENVPN_FLAGS", reader.ForceLowercase(false))
if flagsPtr != nil {
o.Flags = strings.Fields(*flagsPtr)
}
return nil
}
func readOpenVPNProcessUser(r *reader.Reader) (processUser string, err error) {
value, err := r.BoolPtr("OPENVPN_ROOT") // Retro-compatibility
if err != nil {
return "", err
} else if value != nil {
if *value {
return "root", nil
}
const defaultNonRootUser = "nonrootuser"
return defaultNonRootUser, nil
}
return r.String("OPENVPN_PROCESS_USER"), nil
}

View File

@@ -32,6 +32,7 @@ func Test_ivpnAccountID(t *testing.T) {
} }
for _, testCase := range testCases { for _, testCase := range testCases {
testCase := testCase
t.Run(testCase.s, func(t *testing.T) { t.Run(testCase.s, func(t *testing.T) {
t.Parallel() t.Parallel()

View File

@@ -2,14 +2,11 @@ package settings
import ( import (
"fmt" "fmt"
"strings"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers" "github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/constants/providers" "github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/provider/privateinternetaccess/presets" "github.com/qdm12/gluetun/internal/provider/privateinternetaccess/presets"
"github.com/qdm12/gosettings" "github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/reader"
"github.com/qdm12/gosettings/validate" "github.com/qdm12/gosettings/validate"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
) )
@@ -20,10 +17,10 @@ type OpenVPNSelection struct {
// 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 `json:"config_file_path"`
// Protocol is the OpenVPN network protocol to use, // TCP is true if the OpenVPN protocol is TCP,
// and can be udp or tcp. It cannot be the empty string // and false for UDP.
// in the internal state. // It cannot be nil in the internal state.
Protocol string `json:"protocol"` TCP *bool `json:"tcp"`
// 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.
@@ -43,14 +40,8 @@ func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
} }
} }
err = validate.IsOneOf(o.Protocol, constants.UDP, constants.TCP)
if err != nil {
return fmt.Errorf("network protocol: %w", err)
}
// Validate TCP // Validate TCP
if o.Protocol == constants.TCP && helpers.IsOneOf(vpnProvider, if *o.TCP && helpers.IsOneOf(vpnProvider,
providers.Giganews,
providers.Ipvanish, providers.Ipvanish,
providers.Perfectprivacy, providers.Perfectprivacy,
providers.Privado, providers.Privado,
@@ -68,7 +59,7 @@ func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
providers.Privatevpn, providers.Torguard: providers.Privatevpn, providers.Torguard:
// no custom port allowed // no custom port allowed
case providers.Expressvpn, providers.Fastestvpn, case providers.Expressvpn, providers.Fastestvpn,
providers.Giganews, providers.Ipvanish, providers.Nordvpn, providers.Ipvanish, providers.Nordvpn,
providers.Privado, providers.Purevpn, providers.Privado, providers.Purevpn,
providers.Surfshark, providers.VPNSecure, providers.Surfshark, providers.VPNSecure,
providers.VPNUnlimited, providers.Vyprvpn: providers.VPNUnlimited, providers.Vyprvpn:
@@ -112,7 +103,7 @@ func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
} }
allowedPorts := allowedUDP allowedPorts := allowedUDP
if o.Protocol == constants.TCP { if *o.TCP {
allowedPorts = allowedTCP allowedPorts = allowedTCP
} }
err = validate.IsOneOf(*o.CustomPort, allowedPorts...) err = validate.IsOneOf(*o.CustomPort, allowedPorts...)
@@ -141,22 +132,29 @@ 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: gosettings.CopyPointer(o.ConfFile),
Protocol: o.Protocol, TCP: gosettings.CopyPointer(o.TCP),
CustomPort: gosettings.CopyPointer(o.CustomPort), CustomPort: gosettings.CopyPointer(o.CustomPort),
PIAEncPreset: gosettings.CopyPointer(o.PIAEncPreset), PIAEncPreset: gosettings.CopyPointer(o.PIAEncPreset),
} }
} }
func (o *OpenVPNSelection) mergeWith(other OpenVPNSelection) {
o.ConfFile = gosettings.MergeWithPointer(o.ConfFile, other.ConfFile)
o.TCP = gosettings.MergeWithPointer(o.TCP, other.TCP)
o.CustomPort = gosettings.MergeWithPointer(o.CustomPort, other.CustomPort)
o.PIAEncPreset = gosettings.MergeWithPointer(o.PIAEncPreset, other.PIAEncPreset)
}
func (o *OpenVPNSelection) overrideWith(other OpenVPNSelection) { func (o *OpenVPNSelection) overrideWith(other OpenVPNSelection) {
o.ConfFile = gosettings.OverrideWithPointer(o.ConfFile, other.ConfFile) o.ConfFile = gosettings.OverrideWithPointer(o.ConfFile, other.ConfFile)
o.Protocol = gosettings.OverrideWithComparable(o.Protocol, other.Protocol) o.TCP = gosettings.OverrideWithPointer(o.TCP, other.TCP)
o.CustomPort = gosettings.OverrideWithPointer(o.CustomPort, other.CustomPort) o.CustomPort = gosettings.OverrideWithPointer(o.CustomPort, other.CustomPort)
o.PIAEncPreset = gosettings.OverrideWithPointer(o.PIAEncPreset, other.PIAEncPreset) o.PIAEncPreset = gosettings.OverrideWithPointer(o.PIAEncPreset, other.PIAEncPreset)
} }
func (o *OpenVPNSelection) setDefaults(vpnProvider string) { func (o *OpenVPNSelection) setDefaults(vpnProvider string) {
o.ConfFile = gosettings.DefaultPointer(o.ConfFile, "") o.ConfFile = gosettings.DefaultPointer(o.ConfFile, "")
o.Protocol = gosettings.DefaultComparable(o.Protocol, constants.UDP) o.TCP = gosettings.DefaultPointer(o.TCP, false)
o.CustomPort = gosettings.DefaultPointer(o.CustomPort, 0) o.CustomPort = gosettings.DefaultPointer(o.CustomPort, 0)
var defaultEncPreset string var defaultEncPreset string
@@ -172,7 +170,7 @@ func (o OpenVPNSelection) String() string {
func (o OpenVPNSelection) toLinesNode() (node *gotree.Node) { func (o OpenVPNSelection) toLinesNode() (node *gotree.Node) {
node = gotree.New("OpenVPN server selection settings:") node = gotree.New("OpenVPN server selection settings:")
node.Appendf("Protocol: %s", strings.ToUpper(o.Protocol)) node.Appendf("Protocol: %s", helpers.TCPPtrToString(o.TCP))
if *o.CustomPort != 0 { if *o.CustomPort != 0 {
node.Appendf("Custom port: %d", *o.CustomPort) node.Appendf("Custom port: %d", *o.CustomPort)
@@ -188,20 +186,3 @@ func (o OpenVPNSelection) toLinesNode() (node *gotree.Node) {
return node return node
} }
func (o *OpenVPNSelection) read(r *reader.Reader) (err error) {
o.ConfFile = r.Get("OPENVPN_CUSTOM_CONFIG", reader.ForceLowercase(false))
o.Protocol = r.String("OPENVPN_PROTOCOL", reader.RetroKeys("PROTOCOL"))
o.CustomPort, err = r.Uint16Ptr("OPENVPN_ENDPOINT_PORT",
reader.RetroKeys("PORT", "OPENVPN_PORT", "VPN_ENDPOINT_PORT"))
if err != nil {
return err
}
o.PIAEncPreset = r.Get("PRIVATE_INTERNET_ACCESS_OPENVPN_ENCRYPTION_PRESET",
reader.RetroKeys("ENCRYPTION", "PIA_ENCRYPTION"))
return nil
}

View File

@@ -6,7 +6,6 @@ import (
"github.com/qdm12/gluetun/internal/constants/providers" "github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gosettings" "github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/reader"
"github.com/qdm12/gosettings/validate" "github.com/qdm12/gosettings/validate"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
) )
@@ -19,7 +18,7 @@ type PortForwarding struct {
// Provider is set to specify which custom port forwarding code // Provider is set to specify which custom port forwarding code
// should be used. This is especially necessary for the custom // should be used. This is especially necessary for the custom
// provider using Wireguard for a provider where Wireguard is not // provider using Wireguard for a provider where Wireguard is not
// natively supported but custom port forwarding code is available. // natively supported but custom port forwading code is available.
// It defaults to the empty string, meaning the current provider // It defaults to the empty string, meaning the current provider
// should be the one used for port forwarding. // should be the one used for port forwarding.
// It cannot be nil for the internal state. // It cannot be nil for the internal state.
@@ -33,10 +32,6 @@ type PortForwarding struct {
// forwarded port. The redirection is disabled if it is set to 0, which // forwarded port. The redirection is disabled if it is set to 0, which
// is its default as well. // is its default as well.
ListeningPort *uint16 `json:"listening_port"` ListeningPort *uint16 `json:"listening_port"`
// Username is only used for Private Internet Access port forwarding.
Username string `json:"username"`
// Password is only used for Private Internet Access port forwarding.
Password string `json:"password"`
} }
func (p PortForwarding) Validate(vpnProvider string) (err error) { func (p PortForwarding) Validate(vpnProvider string) (err error) {
@@ -50,11 +45,8 @@ func (p PortForwarding) Validate(vpnProvider string) (err error) {
providerSelected = *p.Provider providerSelected = *p.Provider
} }
validProviders := []string{ validProviders := []string{
providers.Perfectprivacy,
providers.PrivateInternetAccess, providers.PrivateInternetAccess,
providers.Privatevpn,
providers.Protonvpn, providers.Protonvpn,
providers.Mullvad,
} }
if err = validate.IsOneOf(providerSelected, validProviders...); err != nil { if err = validate.IsOneOf(providerSelected, validProviders...); err != nil {
return fmt.Errorf("%w: %w", ErrPortForwardingEnabled, err) return fmt.Errorf("%w: %w", ErrPortForwardingEnabled, err)
@@ -68,15 +60,6 @@ func (p PortForwarding) Validate(vpnProvider string) (err error) {
} }
} }
if providerSelected == providers.PrivateInternetAccess {
switch {
case p.Username == "":
return fmt.Errorf("%w", ErrPortForwardingUserEmpty)
case p.Password == "":
return fmt.Errorf("%w", ErrPortForwardingPasswordEmpty)
}
}
return nil return nil
} }
@@ -86,18 +69,21 @@ func (p *PortForwarding) Copy() (copied PortForwarding) {
Provider: gosettings.CopyPointer(p.Provider), Provider: gosettings.CopyPointer(p.Provider),
Filepath: gosettings.CopyPointer(p.Filepath), Filepath: gosettings.CopyPointer(p.Filepath),
ListeningPort: gosettings.CopyPointer(p.ListeningPort), ListeningPort: gosettings.CopyPointer(p.ListeningPort),
Username: p.Username,
Password: p.Password,
} }
} }
func (p *PortForwarding) mergeWith(other PortForwarding) {
p.Enabled = gosettings.MergeWithPointer(p.Enabled, other.Enabled)
p.Provider = gosettings.MergeWithPointer(p.Provider, other.Provider)
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 = gosettings.OverrideWithPointer(p.Enabled, other.Enabled)
p.Provider = gosettings.OverrideWithPointer(p.Provider, other.Provider) p.Provider = gosettings.OverrideWithPointer(p.Provider, other.Provider)
p.Filepath = gosettings.OverrideWithPointer(p.Filepath, other.Filepath) p.Filepath = gosettings.OverrideWithPointer(p.Filepath, other.Filepath)
p.ListeningPort = gosettings.OverrideWithPointer(p.ListeningPort, other.ListeningPort) p.ListeningPort = gosettings.OverrideWithPointer(p.ListeningPort, other.ListeningPort)
p.Username = gosettings.OverrideWithComparable(p.Username, other.Username)
p.Password = gosettings.OverrideWithComparable(p.Password, other.Password)
} }
func (p *PortForwarding) setDefaults() { func (p *PortForwarding) setDefaults() {
@@ -136,54 +122,5 @@ func (p PortForwarding) toLinesNode() (node *gotree.Node) {
} }
node.Appendf("Forwarded port file path: %s", filepath) node.Appendf("Forwarded port file path: %s", filepath)
if p.Username != "" {
credentialsNode := node.Appendf("Credentials:")
credentialsNode.Appendf("Username: %s", p.Username)
credentialsNode.Appendf("Password: %s", gosettings.ObfuscateKey(p.Password))
}
return node return node
} }
func (p *PortForwarding) read(r *reader.Reader) (err error) {
p.Enabled, err = r.BoolPtr("VPN_PORT_FORWARDING",
reader.RetroKeys(
"PORT_FORWARDING",
"PRIVATE_INTERNET_ACCESS_VPN_PORT_FORWARDING",
))
if err != nil {
return err
}
p.Provider = r.Get("VPN_PORT_FORWARDING_PROVIDER")
p.Filepath = r.Get("VPN_PORT_FORWARDING_STATUS_FILE",
reader.ForceLowercase(false),
reader.RetroKeys(
"PORT_FORWARDING_STATUS_FILE",
"PRIVATE_INTERNET_ACCESS_VPN_PORT_FORWARDING_STATUS_FILE",
))
p.ListeningPort, err = r.Uint16Ptr("VPN_PORT_FORWARDING_LISTENING_PORT")
if err != nil {
return err
}
usernameKeys := []string{"VPN_PORT_FORWARDING_USERNAME", "OPENVPN_USER", "USER"}
for _, key := range usernameKeys {
p.Username = r.String(key, reader.ForceLowercase(false))
if p.Username != "" {
break
}
}
passwordKeys := []string{"VPN_PORT_FORWARDING_PASSWORD", "OPENVPN_PASSWORD", "PASSWORD"}
for _, key := range passwordKeys {
p.Password = r.String(key, reader.ForceLowercase(false))
if p.Password != "" {
break
}
}
return nil
}

View File

@@ -10,7 +10,7 @@ func Test_PortForwarding_String(t *testing.T) {
t.Parallel() t.Parallel()
settings := PortForwarding{ settings := PortForwarding{
Enabled: ptrTo(false), Enabled: boolPtr(false),
} }
s := settings.String() s := settings.String()

View File

@@ -2,12 +2,10 @@ package settings
import ( import (
"fmt" "fmt"
"strings"
"github.com/qdm12/gluetun/internal/constants/providers" "github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/constants/vpn" "github.com/qdm12/gluetun/internal/constants/vpn"
"github.com/qdm12/gosettings" "github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/reader"
"github.com/qdm12/gosettings/validate" "github.com/qdm12/gosettings/validate"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
) )
@@ -15,8 +13,8 @@ import (
// Provider contains settings specific to a VPN provider. // Provider contains settings specific to a VPN provider.
type Provider struct { type Provider struct {
// Name is the VPN service provider name. // Name is the VPN service provider name.
// It cannot be the empty string in the internal state. // It cannot be nil in the internal state.
Name string `json:"name"` Name *string `json:"name"`
// 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 `json:"server_selection"`
@@ -25,7 +23,7 @@ type Provider struct {
} }
// TODO v4 remove pointer for receiver (because of Surfshark). // TODO v4 remove pointer for receiver (because of Surfshark).
func (p *Provider) validate(vpnType string, filterChoicesGetter FilterChoicesGetter, warner Warner) (err error) { func (p *Provider) validate(vpnType string, storage Storage) (err error) {
// Validate Name // Validate Name
var validNames []string var validNames []string
if vpnType == vpn.OpenVPN { if vpnType == vpn.OpenVPN {
@@ -35,25 +33,23 @@ func (p *Provider) validate(vpnType string, filterChoicesGetter FilterChoicesGet
validNames = []string{ validNames = []string{
providers.Airvpn, providers.Airvpn,
providers.Custom, providers.Custom,
providers.Fastestvpn,
providers.Ivpn, providers.Ivpn,
providers.Mullvad, providers.Mullvad,
providers.Nordvpn, providers.Nordvpn,
providers.Protonvpn,
providers.Surfshark, providers.Surfshark,
providers.Windscribe, providers.Windscribe,
} }
} }
if err = validate.IsOneOf(p.Name, validNames...); err != nil { if err = validate.IsOneOf(*p.Name, validNames...); err != nil {
return fmt.Errorf("%w for Wireguard: %w", ErrVPNProviderNameNotValid, err) return fmt.Errorf("%w for Wireguard: %w", ErrVPNProviderNameNotValid, err)
} }
err = p.ServerSelection.validate(p.Name, filterChoicesGetter, warner) err = p.ServerSelection.validate(*p.Name, storage)
if err != nil { if err != nil {
return fmt.Errorf("server selection: %w", err) return fmt.Errorf("server selection: %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: %w", err)
} }
@@ -63,22 +59,28 @@ func (p *Provider) validate(vpnType string, filterChoicesGetter FilterChoicesGet
func (p *Provider) copy() (copied Provider) { func (p *Provider) copy() (copied Provider) {
return Provider{ return Provider{
Name: p.Name, Name: gosettings.CopyPointer(p.Name),
ServerSelection: p.ServerSelection.copy(), ServerSelection: p.ServerSelection.copy(),
PortForwarding: p.PortForwarding.Copy(), PortForwarding: p.PortForwarding.Copy(),
} }
} }
func (p *Provider) mergeWith(other Provider) {
p.Name = gosettings.MergeWithPointer(p.Name, other.Name)
p.ServerSelection.mergeWith(other.ServerSelection)
p.PortForwarding.mergeWith(other.PortForwarding)
}
func (p *Provider) overrideWith(other Provider) { func (p *Provider) overrideWith(other Provider) {
p.Name = gosettings.OverrideWithComparable(p.Name, other.Name) p.Name = gosettings.OverrideWithPointer(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.DefaultComparable(p.Name, providers.PrivateInternetAccess) p.Name = gosettings.DefaultPointer(p.Name, providers.PrivateInternetAccess)
p.ServerSelection.setDefaults(*p.Name)
p.PortForwarding.setDefaults() p.PortForwarding.setDefaults()
p.ServerSelection.setDefaults(p.Name, *p.PortForwarding.Enabled)
} }
func (p Provider) String() string { func (p Provider) String() string {
@@ -87,42 +89,8 @@ func (p Provider) String() string {
func (p Provider) toLinesNode() (node *gotree.Node) { func (p Provider) toLinesNode() (node *gotree.Node) {
node = gotree.New("VPN provider settings:") node = gotree.New("VPN provider settings:")
node.Appendf("Name: %s", p.Name) node.Appendf("Name: %s", *p.Name)
node.AppendNode(p.ServerSelection.toLinesNode()) node.AppendNode(p.ServerSelection.toLinesNode())
node.AppendNode(p.PortForwarding.toLinesNode()) node.AppendNode(p.PortForwarding.toLinesNode())
return node return node
} }
func (p *Provider) read(r *reader.Reader, vpnType string) (err error) {
p.Name = readVPNServiceProvider(r, vpnType)
err = p.ServerSelection.read(r, p.Name, vpnType)
if err != nil {
return fmt.Errorf("server selection: %w", err)
}
err = p.PortForwarding.read(r)
if err != nil {
return fmt.Errorf("port forwarding: %w", err)
}
return nil
}
func readVPNServiceProvider(r *reader.Reader, vpnType string) (vpnProvider string) {
vpnProvider = r.String("VPN_SERVICE_PROVIDER", reader.RetroKeys("VPNSP"))
if vpnProvider == "" {
if vpnType != vpn.Wireguard && r.Get("OPENVPN_CUSTOM_CONFIG") != nil {
// retro compatibility
return providers.Custom
}
return ""
}
vpnProvider = strings.ToLower(vpnProvider)
if vpnProvider == "pia" { // retro compatibility
return providers.PrivateInternetAccess
}
return vpnProvider
}

View File

@@ -3,37 +3,33 @@ package settings
import ( import (
"fmt" "fmt"
"path/filepath" "path/filepath"
"time"
"github.com/qdm12/gluetun/internal/publicip/api" "github.com/qdm12/gluetun/internal/publicip/api"
"github.com/qdm12/gosettings" "github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/reader"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
) )
// PublicIP contains settings for port forwarding. // PublicIP contains settings for port forwarding.
type PublicIP struct { type PublicIP struct {
// Enabled is set to true to fetch the public ip address // Period is the period to get the public IP address.
// information on VPN connection. It defaults to true. // It can be set to 0 to disable periodic checking.
Enabled *bool // It cannot be nil for the internal state.
// TODO change to value and add enabled field
Period *time.Duration
// IPFilepath is the public IP address status file path // IPFilepath is the public IP address 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
IPFilepath *string IPFilepath *string
// APIs is the list of public ip APIs to use to fetch public IP information. // API is the API name to use to fetch public IP information.
// If there is more than one API, the first one is used // It can be ipinfo or ip2location. It defaults to ipinfo.
// by default and the others are used as fallbacks in case of API string
// the service rate limiting us. It defaults to use all services, // APIToken is the token to use for the IP data service
// with the first one being ipinfo.io for historical reasons. // such as ipinfo.io. It can be the empty string to
APIs []PublicIPAPI // indicate not to use a token. It cannot be nil for the
} // internal state.
APIToken *string
type PublicIPAPI struct {
// Name is the name of the public ip API service.
// It can be "cloudflare", "ifconfigco", "ip2location" or "ipinfo".
Name string
// Token is the token to use for the public ip API service.
Token string
} }
// UpdateWith deep copies the receiving settings, overrides the copy with // UpdateWith deep copies the receiving settings, overrides the copy with
@@ -51,6 +47,12 @@ func (p PublicIP) UpdateWith(partialUpdate PublicIP) (updatedSettings PublicIP,
} }
func (p PublicIP) validate() (err error) { func (p PublicIP) validate() (err error) {
const minPeriod = 5 * time.Second
if *p.Period < minPeriod {
return fmt.Errorf("%w: %s must be at least %s",
ErrPublicIPPeriodTooShort, p.Period, minPeriod)
}
if *p.IPFilepath != "" { // optional if *p.IPFilepath != "" { // optional
_, err := filepath.Abs(*p.IPFilepath) _, err := filepath.Abs(*p.IPFilepath)
if err != nil { if err != nil {
@@ -58,39 +60,43 @@ func (p PublicIP) validate() (err error) {
} }
} }
for _, publicIPAPI := range p.APIs { _, err = api.ParseProvider(p.API)
_, err = api.ParseProvider(publicIPAPI.Name)
if err != nil { if err != nil {
return fmt.Errorf("API name: %w", err) 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{
Enabled: gosettings.CopyPointer(p.Enabled), Period: gosettings.CopyPointer(p.Period),
IPFilepath: gosettings.CopyPointer(p.IPFilepath), IPFilepath: gosettings.CopyPointer(p.IPFilepath),
APIs: gosettings.CopySlice(p.APIs), API: p.API,
APIToken: gosettings.CopyPointer(p.APIToken),
} }
} }
func (p *PublicIP) mergeWith(other PublicIP) {
p.Period = gosettings.MergeWithPointer(p.Period, other.Period)
p.IPFilepath = gosettings.MergeWithPointer(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.Enabled = gosettings.OverrideWithPointer(p.Enabled, other.Enabled) p.Period = gosettings.OverrideWithPointer(p.Period, other.Period)
p.IPFilepath = gosettings.OverrideWithPointer(p.IPFilepath, other.IPFilepath) p.IPFilepath = gosettings.OverrideWithPointer(p.IPFilepath, other.IPFilepath)
p.APIs = gosettings.OverrideWithSlice(p.APIs, other.APIs) p.API = gosettings.OverrideWithString(p.API, other.API)
p.APIToken = gosettings.OverrideWithPointer(p.APIToken, other.APIToken)
} }
func (p *PublicIP) setDefaults() { func (p *PublicIP) setDefaults() {
p.Enabled = gosettings.DefaultPointer(p.Enabled, true) const defaultPeriod = 12 * time.Hour
p.Period = gosettings.DefaultPointer(p.Period, defaultPeriod)
p.IPFilepath = gosettings.DefaultPointer(p.IPFilepath, "/tmp/gluetun/ip") p.IPFilepath = gosettings.DefaultPointer(p.IPFilepath, "/tmp/gluetun/ip")
p.APIs = gosettings.DefaultSlice(p.APIs, []PublicIPAPI{ p.API = gosettings.DefaultString(p.API, "ipinfo")
{Name: string(api.IPInfo)}, p.APIToken = gosettings.DefaultPointer(p.APIToken, "")
{Name: string(api.Cloudflare)},
{Name: string(api.IfConfigCo)},
{Name: string(api.IP2Location)},
})
} }
func (p PublicIP) String() string { func (p PublicIP) String() string {
@@ -98,78 +104,28 @@ func (p PublicIP) String() string {
} }
func (p PublicIP) toLinesNode() (node *gotree.Node) { func (p PublicIP) toLinesNode() (node *gotree.Node) {
if !*p.Enabled { node = gotree.New("Public IP settings:")
return gotree.New("Public IP settings: disabled")
if *p.Period == 0 {
node.Appendf("Enabled: no")
return node
} }
node = gotree.New("Public IP settings:") updatePeriod := "disabled"
if *p.Period > 0 {
updatePeriod = "every " + p.Period.String()
}
node.Appendf("Fetching: %s", updatePeriod)
if *p.IPFilepath != "" { if *p.IPFilepath != "" {
node.Appendf("IP file path: %s", *p.IPFilepath) node.Appendf("IP file path: %s", *p.IPFilepath)
} }
baseAPIString := "Public IP data base API: " + p.APIs[0].Name node.Appendf("Public IP data API: %s", p.API)
if p.APIs[0].Token != "" {
baseAPIString += " (token " + gosettings.ObfuscateKey(p.APIs[0].Token) + ")" if *p.APIToken != "" {
} node.Appendf("API token: %s", gosettings.ObfuscateKey(*p.APIToken))
node.Append(baseAPIString)
if len(p.APIs) > 1 {
backupAPIsNode := node.Append("Public IP data backup APIs:")
for i := 1; i < len(p.APIs); i++ {
message := p.APIs[i].Name
if p.APIs[i].Token != "" {
message += " (token " + gosettings.ObfuscateKey(p.APIs[i].Token) + ")"
}
backupAPIsNode.Append(message)
}
} }
return node return node
} }
func (p *PublicIP) read(r *reader.Reader, warner Warner) (err error) {
p.Enabled, err = readPublicIPEnabled(r, warner)
if err != nil {
return err
}
p.IPFilepath = r.Get("PUBLICIP_FILE",
reader.ForceLowercase(false), reader.RetroKeys("IP_STATUS_FILE"))
apiNames := r.CSV("PUBLICIP_API")
if len(apiNames) > 0 {
apiTokens := r.CSV("PUBLICIP_API_TOKEN")
p.APIs = make([]PublicIPAPI, len(apiNames))
for i := range apiNames {
p.APIs[i].Name = apiNames[i]
var token string
if i < len(apiTokens) { // only set token if it exists
token = apiTokens[i]
}
p.APIs[i].Token = token
}
}
return nil
}
func readPublicIPEnabled(r *reader.Reader, warner Warner) (
enabled *bool, err error,
) {
periodPtr, err := r.DurationPtr("PUBLICIP_PERIOD") // Retro-compatibility
if err != nil {
return nil, err
} else if periodPtr == nil {
return r.BoolPtr("PUBLICIP_ENABLED")
}
if *periodPtr == 0 {
warner.Warn("please replace PUBLICIP_PERIOD=0 with PUBLICIP_ENABLED=no")
return ptrTo(false), nil
}
warner.Warn("PUBLICIP_PERIOD is no longer used. " +
"It is assumed from its non-zero value you want PUBLICIP_ENABLED=yes. " +
"Please migrate to use PUBLICIP_ENABLED only in the future.")
return ptrTo(true), nil
}

View File

@@ -1,161 +0,0 @@
package settings
import (
"testing"
"github.com/golang/mock/gomock"
"github.com/qdm12/gosettings/reader"
"github.com/stretchr/testify/assert"
)
func Test_PublicIP_read(t *testing.T) {
t.Parallel()
testCases := map[string]struct {
makeReader func(ctrl *gomock.Controller) *reader.Reader
makeWarner func(ctrl *gomock.Controller) Warner
settings PublicIP
errWrapped error
errMessage string
}{
"nothing_read": {
makeReader: func(ctrl *gomock.Controller) *reader.Reader {
source := newMockSource(ctrl, []sourceKeyValue{
{key: "PUBLICIP_PERIOD"},
{key: "PUBLICIP_ENABLED"},
{key: "IP_STATUS_FILE"},
{key: "PUBLICIP_FILE"},
{key: "PUBLICIP_API"},
})
return reader.New(reader.Settings{
Sources: []reader.Source{source},
})
},
},
"single_api_no_token": {
makeReader: func(ctrl *gomock.Controller) *reader.Reader {
source := newMockSource(ctrl, []sourceKeyValue{
{key: "PUBLICIP_PERIOD"},
{key: "PUBLICIP_ENABLED"},
{key: "IP_STATUS_FILE"},
{key: "PUBLICIP_FILE"},
{key: "PUBLICIP_API", value: "ipinfo"},
{key: "PUBLICIP_API_TOKEN"},
})
return reader.New(reader.Settings{
Sources: []reader.Source{source},
})
},
settings: PublicIP{
APIs: []PublicIPAPI{
{Name: "ipinfo"},
},
},
},
"single_api_with_token": {
makeReader: func(ctrl *gomock.Controller) *reader.Reader {
source := newMockSource(ctrl, []sourceKeyValue{
{key: "PUBLICIP_PERIOD"},
{key: "PUBLICIP_ENABLED"},
{key: "IP_STATUS_FILE"},
{key: "PUBLICIP_FILE"},
{key: "PUBLICIP_API", value: "ipinfo"},
{key: "PUBLICIP_API_TOKEN", value: "xyz"},
})
return reader.New(reader.Settings{
Sources: []reader.Source{source},
})
},
settings: PublicIP{
APIs: []PublicIPAPI{
{Name: "ipinfo", Token: "xyz"},
},
},
},
"multiple_apis_no_token": {
makeReader: func(ctrl *gomock.Controller) *reader.Reader {
source := newMockSource(ctrl, []sourceKeyValue{
{key: "PUBLICIP_PERIOD"},
{key: "PUBLICIP_ENABLED"},
{key: "IP_STATUS_FILE"},
{key: "PUBLICIP_FILE"},
{key: "PUBLICIP_API", value: "ipinfo,ip2location"},
{key: "PUBLICIP_API_TOKEN"},
})
return reader.New(reader.Settings{
Sources: []reader.Source{source},
})
},
settings: PublicIP{
APIs: []PublicIPAPI{
{Name: "ipinfo"},
{Name: "ip2location"},
},
},
},
"multiple_apis_with_token": {
makeReader: func(ctrl *gomock.Controller) *reader.Reader {
source := newMockSource(ctrl, []sourceKeyValue{
{key: "PUBLICIP_PERIOD"},
{key: "PUBLICIP_ENABLED"},
{key: "IP_STATUS_FILE"},
{key: "PUBLICIP_FILE"},
{key: "PUBLICIP_API", value: "ipinfo,ip2location"},
{key: "PUBLICIP_API_TOKEN", value: "xyz,abc"},
})
return reader.New(reader.Settings{
Sources: []reader.Source{source},
})
},
settings: PublicIP{
APIs: []PublicIPAPI{
{Name: "ipinfo", Token: "xyz"},
{Name: "ip2location", Token: "abc"},
},
},
},
"multiple_apis_with_and_without_token": {
makeReader: func(ctrl *gomock.Controller) *reader.Reader {
source := newMockSource(ctrl, []sourceKeyValue{
{key: "PUBLICIP_PERIOD"},
{key: "PUBLICIP_ENABLED"},
{key: "IP_STATUS_FILE"},
{key: "PUBLICIP_FILE"},
{key: "PUBLICIP_API", value: "ipinfo,ip2location"},
{key: "PUBLICIP_API_TOKEN", value: "xyz"},
})
return reader.New(reader.Settings{
Sources: []reader.Source{source},
})
},
settings: PublicIP{
APIs: []PublicIPAPI{
{Name: "ipinfo", Token: "xyz"},
{Name: "ip2location"},
},
},
},
}
for name, testCase := range testCases {
t.Run(name, func(t *testing.T) {
t.Parallel()
ctrl := gomock.NewController(t)
reader := testCase.makeReader(ctrl)
var warner Warner
if testCase.makeWarner != nil {
warner = testCase.makeWarner(ctrl)
}
var settings PublicIP
err := settings.read(reader, warner)
assert.Equal(t, testCase.settings, settings)
assert.ErrorIs(t, err, testCase.errWrapped)
if testCase.errWrapped != nil {
assert.EqualError(t, err, testCase.errMessage)
}
})
}
}

View File

@@ -7,7 +7,6 @@ import (
"strconv" "strconv"
"github.com/qdm12/gosettings" "github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/reader"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
) )
@@ -19,11 +18,6 @@ type ControlServer struct {
// 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
// AuthFilePath is the path to the file containing the authentication
// configuration for the middleware.
// It cannot be empty in the internal state and defaults to
// /gluetun/auth/config.toml.
AuthFilePath string
} }
func (c ControlServer) validate() (err error) { func (c ControlServer) validate() (err error) {
@@ -51,23 +45,27 @@ func (c *ControlServer) copy() (copied ControlServer) {
return ControlServer{ return ControlServer{
Address: gosettings.CopyPointer(c.Address), Address: gosettings.CopyPointer(c.Address),
Log: gosettings.CopyPointer(c.Log), Log: gosettings.CopyPointer(c.Log),
AuthFilePath: c.AuthFilePath,
} }
} }
// mergeWith merges the other settings into any
// unset field of the receiver settings object.
func (c *ControlServer) mergeWith(other ControlServer) {
c.Address = gosettings.MergeWithPointer(c.Address, other.Address)
c.Log = gosettings.MergeWithPointer(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.Address = gosettings.OverrideWithPointer(c.Address, other.Address)
c.Log = gosettings.OverrideWithPointer(c.Log, other.Log) c.Log = gosettings.OverrideWithPointer(c.Log, other.Log)
c.AuthFilePath = gosettings.OverrideWithComparable(c.AuthFilePath, other.AuthFilePath)
} }
func (c *ControlServer) setDefaults() { func (c *ControlServer) setDefaults() {
c.Address = gosettings.DefaultPointer(c.Address, ":8000") c.Address = gosettings.DefaultPointer(c.Address, ":8000")
c.Log = gosettings.DefaultPointer(c.Log, true) c.Log = gosettings.DefaultPointer(c.Log, true)
c.AuthFilePath = gosettings.DefaultComparable(c.AuthFilePath, "/gluetun/auth/config.toml")
} }
func (c ControlServer) String() string { func (c ControlServer) String() string {
@@ -78,19 +76,5 @@ 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 address: %s", *c.Address)
node.Appendf("Logging: %s", gosettings.BoolToYesNo(c.Log)) node.Appendf("Logging: %s", gosettings.BoolToYesNo(c.Log))
node.Appendf("Authentication file path: %s", c.AuthFilePath)
return node return node
} }
func (c *ControlServer) read(r *reader.Reader) (err error) {
c.Log, err = r.BoolPtr("HTTP_CONTROL_SERVER_LOG")
if err != nil {
return err
}
c.Address = r.Get("HTTP_CONTROL_SERVER_ADDRESS")
c.AuthFilePath = r.String("HTTP_CONTROL_SERVER_AUTH_CONFIG_FILEPATH")
return nil
}

View File

@@ -12,7 +12,6 @@ import (
"github.com/qdm12/gluetun/internal/constants/vpn" "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"
"github.com/qdm12/gosettings/reader"
"github.com/qdm12/gosettings/validate" "github.com/qdm12/gosettings/validate"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
) )
@@ -55,21 +54,14 @@ type ServerSelection struct { //nolint:maligned
// TODO extend to providers using FreeOnly. // TODO extend to providers using FreeOnly.
PremiumOnly *bool `json:"premium_only"` PremiumOnly *bool `json:"premium_only"`
// StreamOnly is true if VPN servers not for streaming should // StreamOnly is true if VPN servers not for streaming should
// be filtered. This is used with ProtonVPN and VPNUnlimited. // be filtered. This is used with VPNUnlimited.
StreamOnly *bool `json:"stream_only"` StreamOnly *bool `json:"stream_only"`
// MultiHopOnly is true if VPN servers that are not multihop // 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 `json:"multi_hop_only"`
// PortForwardOnly is true if VPN servers that don't support // PortForwardOnly is true if VPN servers that don't support
// port forwarding should be filtered. This is used with PIA // port forwarding should be filtered. This is used with PIA.
// and ProtonVPN.
PortForwardOnly *bool `json:"port_forward_only"` PortForwardOnly *bool `json:"port_forward_only"`
// SecureCoreOnly is true if VPN servers without secure core should
// be filtered. This is used with ProtonVPN.
SecureCoreOnly *bool `json:"secure_core_only"`
// TorOnly is true if VPN servers without tor should
// be filtered. This is used with ProtonVPN.
TorOnly *bool `json:"tor_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 `json:"openvpn"`
@@ -86,20 +78,17 @@ var (
ErrMultiHopOnlyNotSupported = errors.New("multi hop 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") ErrPortForwardOnlyNotSupported = errors.New("port forwarding only filter is not supported")
ErrFreePremiumBothSet = errors.New("free only and premium only filters are both set") ErrFreePremiumBothSet = errors.New("free only and premium only filters are both set")
ErrSecureCoreOnlyNotSupported = errors.New("secure core only filter is not supported")
ErrTorOnlyNotSupported = errors.New("tor only filter is not supported")
) )
func (ss *ServerSelection) validate(vpnServiceProvider string, func (ss *ServerSelection) validate(vpnServiceProvider string,
filterChoicesGetter FilterChoicesGetter, warner Warner, storage Storage) (err error) {
) (err error) {
switch ss.VPN { switch ss.VPN {
case vpn.OpenVPN, vpn.Wireguard: case vpn.OpenVPN, vpn.Wireguard:
default: default:
return fmt.Errorf("%w: %s", ErrVPNTypeNotValid, ss.VPN) return fmt.Errorf("%w: %s", ErrVPNTypeNotValid, ss.VPN)
} }
filterChoices, err := getLocationFilterChoices(vpnServiceProvider, ss, filterChoicesGetter, warner) filterChoices, err := getLocationFilterChoices(vpnServiceProvider, ss, storage)
if err != nil { if err != nil {
return err // already wrapped error return err // already wrapped error
} }
@@ -112,19 +101,60 @@ func (ss *ServerSelection) validate(vpnServiceProvider string,
*ss = surfsharkRetroRegion(*ss) *ss = surfsharkRetroRegion(*ss)
} }
err = validateServerFilters(*ss, filterChoices, vpnServiceProvider, warner) err = validateServerFilters(*ss, filterChoices)
if err != nil { if err != nil {
return fmt.Errorf("for VPN service provider %s: %w", vpnServiceProvider, err) return fmt.Errorf("for VPN service provider %s: %w", vpnServiceProvider, err)
} }
err = validateSubscriptionTierFilters(*ss, vpnServiceProvider) if *ss.OwnedOnly &&
if err != nil { vpnServiceProvider != providers.Mullvad {
return fmt.Errorf("for VPN service provider %s: %w", vpnServiceProvider, err) return fmt.Errorf("%w: for VPN service provider %s",
ErrOwnedOnlyNotSupported, vpnServiceProvider)
} }
err = validateFeatureFilters(*ss, vpnServiceProvider) if *ss.FreeOnly &&
if err != nil { !helpers.IsOneOf(vpnServiceProvider,
return fmt.Errorf("for VPN service provider %s: %w", vpnServiceProvider, err) 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 { if ss.VPN == vpn.OpenVPN {
@@ -143,20 +173,19 @@ func (ss *ServerSelection) validate(vpnServiceProvider string,
} }
func getLocationFilterChoices(vpnServiceProvider string, func getLocationFilterChoices(vpnServiceProvider string,
ss *ServerSelection, filterChoicesGetter FilterChoicesGetter, warner Warner) ( ss *ServerSelection, storage Storage) (filterChoices models.FilterChoices,
filterChoices models.FilterChoices, err error, err error) {
) { filterChoices = storage.GetFilterChoices(vpnServiceProvider)
filterChoices = filterChoicesGetter.GetFilterChoices(vpnServiceProvider)
if vpnServiceProvider == providers.Surfshark { if vpnServiceProvider == providers.Surfshark {
// // Retro compatibility // // Retro compatibility
// TODO v4 remove // TODO v4 remove
newAndRetroRegions := append(filterChoices.Regions, validation.SurfsharkRetroLocChoices()...) //nolint:gocritic newAndRetroRegions := append(filterChoices.Regions, validation.SurfsharkRetroLocChoices()...) //nolint:gocritic
err := atLeastOneIsOneOfCaseInsensitive(ss.Regions, newAndRetroRegions, warner) err := validate.AreAllOneOfCaseInsensitive(ss.Regions, newAndRetroRegions)
if err != nil { if err != nil {
// Only return error comparing with newer regions, we don't want to confuse the user // Only return error comparing with newer regions, we don't want to confuse the user
// with the retro regions in the error message. // with the retro regions in the error message.
err = atLeastOneIsOneOfCaseInsensitive(ss.Regions, filterChoices.Regions, warner) err = validate.AreAllOneOfCaseInsensitive(ss.Regions, filterChoices.Regions)
return models.FilterChoices{}, fmt.Errorf("%w: %w", ErrRegionNotValid, err) return models.FilterChoices{}, fmt.Errorf("%w: %w", ErrRegionNotValid, err)
} }
} }
@@ -166,54 +195,38 @@ func getLocationFilterChoices(vpnServiceProvider string,
// validateServerFilters validates filters against the choices given as arguments. // validateServerFilters validates filters against the choices given as arguments.
// Set an argument to nil to pass the check for a particular filter. // Set an argument to nil to pass the check for a particular filter.
func validateServerFilters(settings ServerSelection, filterChoices models.FilterChoices, func validateServerFilters(settings ServerSelection, filterChoices models.FilterChoices) (err error) {
vpnServiceProvider string, warner Warner, err = validate.AreAllOneOfCaseInsensitive(settings.Countries, filterChoices.Countries)
) (err error) {
err = atLeastOneIsOneOfCaseInsensitive(settings.Countries, filterChoices.Countries, warner)
if err != nil { if err != nil {
return fmt.Errorf("%w: %w", ErrCountryNotValid, err) return fmt.Errorf("%w: %w", ErrCountryNotValid, err)
} }
err = atLeastOneIsOneOfCaseInsensitive(settings.Regions, filterChoices.Regions, warner) err = validate.AreAllOneOfCaseInsensitive(settings.Regions, filterChoices.Regions)
if err != nil { if err != nil {
return fmt.Errorf("%w: %w", ErrRegionNotValid, err) return fmt.Errorf("%w: %w", ErrRegionNotValid, err)
} }
err = atLeastOneIsOneOfCaseInsensitive(settings.Cities, filterChoices.Cities, warner) err = validate.AreAllOneOfCaseInsensitive(settings.Cities, filterChoices.Cities)
if err != nil { if err != nil {
return fmt.Errorf("%w: %w", ErrCityNotValid, err) return fmt.Errorf("%w: %w", ErrCityNotValid, err)
} }
err = atLeastOneIsOneOfCaseInsensitive(settings.ISPs, filterChoices.ISPs, warner) err = validate.AreAllOneOfCaseInsensitive(settings.ISPs, filterChoices.ISPs)
if err != nil { if err != nil {
return fmt.Errorf("%w: %w", ErrISPNotValid, err) return fmt.Errorf("%w: %w", ErrISPNotValid, err)
} }
err = atLeastOneIsOneOfCaseInsensitive(settings.Hostnames, filterChoices.Hostnames, warner) err = validate.AreAllOneOfCaseInsensitive(settings.Hostnames, filterChoices.Hostnames)
if err != nil { if err != nil {
return fmt.Errorf("%w: %w", ErrHostnameNotValid, err) return fmt.Errorf("%w: %w", ErrHostnameNotValid, err)
} }
if vpnServiceProvider == providers.Custom { err = validate.AreAllOneOfCaseInsensitive(settings.Names, filterChoices.Names)
switch len(settings.Names) {
case 0:
case 1:
// Allow a single name to be specified for the custom provider in case
// the user wants to use VPN server side port forwarding with PIA
// which requires a server name for TLS verification.
filterChoices.Names = settings.Names
default:
return fmt.Errorf("%w: %d names specified instead of "+
"0 or 1 for the custom provider",
ErrNameNotValid, len(settings.Names))
}
}
err = atLeastOneIsOneOfCaseInsensitive(settings.Names, filterChoices.Names, warner)
if err != nil { if err != nil {
return fmt.Errorf("%w: %w", ErrNameNotValid, err) return fmt.Errorf("%w: %w", ErrNameNotValid, err)
} }
err = atLeastOneIsOneOfCaseInsensitive(settings.Categories, filterChoices.Categories, warner) err = validate.AreAllOneOfCaseInsensitive(settings.Categories, filterChoices.Categories)
if err != nil { if err != nil {
return fmt.Errorf("%w: %w", ErrCategoryNotValid, err) return fmt.Errorf("%w: %w", ErrCategoryNotValid, err)
} }
@@ -221,81 +234,6 @@ func validateServerFilters(settings ServerSelection, filterChoices models.Filter
return nil return nil
} }
func atLeastOneIsOneOfCaseInsensitive(values, choices []string,
warner Warner,
) (err error) {
if len(values) > 0 && len(choices) == 0 {
return fmt.Errorf("%w", validate.ErrNoChoice)
}
set := make(map[string]struct{}, len(choices))
for _, choice := range choices {
lowercaseChoice := strings.ToLower(choice)
set[lowercaseChoice] = struct{}{}
}
invalidValues := make([]string, 0, len(values))
for _, value := range values {
lowercaseValue := strings.ToLower(value)
_, ok := set[lowercaseValue]
if ok {
continue
}
invalidValues = append(invalidValues, value)
}
switch len(invalidValues) {
case 0:
return nil
case len(values):
return fmt.Errorf("%w: none of %s is one of the choices available %s",
validate.ErrValueNotOneOf, strings.Join(values, ", "), strings.Join(choices, ", "))
default:
warner.Warn(fmt.Sprintf("values %s are not in choices %s",
strings.Join(invalidValues, ", "), strings.Join(choices, ", ")))
}
return nil
}
func validateSubscriptionTierFilters(settings ServerSelection, vpnServiceProvider string) error {
switch {
case *settings.FreeOnly &&
!helpers.IsOneOf(vpnServiceProvider, providers.Protonvpn, providers.VPNUnlimited):
return fmt.Errorf("%w", ErrFreeOnlyNotSupported)
case *settings.PremiumOnly &&
!helpers.IsOneOf(vpnServiceProvider, providers.VPNSecure):
return fmt.Errorf("%w", ErrPremiumOnlyNotSupported)
case *settings.FreeOnly && *settings.PremiumOnly:
return fmt.Errorf("%w", ErrFreePremiumBothSet)
default:
return nil
}
}
func validateFeatureFilters(settings ServerSelection, vpnServiceProvider string) error {
switch {
case *settings.OwnedOnly && vpnServiceProvider != providers.Mullvad:
return fmt.Errorf("%w", ErrOwnedOnlyNotSupported)
case vpnServiceProvider == providers.Protonvpn && *settings.FreeOnly && *settings.PortForwardOnly:
return fmt.Errorf("%w: together with free only filter", ErrPortForwardOnlyNotSupported)
case *settings.StreamOnly &&
!helpers.IsOneOf(vpnServiceProvider, providers.Protonvpn, providers.VPNUnlimited):
return fmt.Errorf("%w", ErrStreamOnlyNotSupported)
case *settings.MultiHopOnly && vpnServiceProvider != providers.Surfshark:
return fmt.Errorf("%w", ErrMultiHopOnlyNotSupported)
case *settings.PortForwardOnly &&
!helpers.IsOneOf(vpnServiceProvider, providers.PrivateInternetAccess, providers.Protonvpn):
return fmt.Errorf("%w", ErrPortForwardOnlyNotSupported)
case *settings.SecureCoreOnly && vpnServiceProvider != providers.Protonvpn:
return fmt.Errorf("%w", ErrSecureCoreOnlyNotSupported)
case *settings.TorOnly && vpnServiceProvider != providers.Protonvpn:
return fmt.Errorf("%w", ErrTorOnlyNotSupported)
default:
return nil
}
}
func (ss *ServerSelection) copy() (copied ServerSelection) { func (ss *ServerSelection) copy() (copied ServerSelection) {
return ServerSelection{ return ServerSelection{
VPN: ss.VPN, VPN: ss.VPN,
@@ -312,8 +250,6 @@ func (ss *ServerSelection) copy() (copied ServerSelection) {
FreeOnly: gosettings.CopyPointer(ss.FreeOnly), FreeOnly: gosettings.CopyPointer(ss.FreeOnly),
PremiumOnly: gosettings.CopyPointer(ss.PremiumOnly), PremiumOnly: gosettings.CopyPointer(ss.PremiumOnly),
StreamOnly: gosettings.CopyPointer(ss.StreamOnly), StreamOnly: gosettings.CopyPointer(ss.StreamOnly),
SecureCoreOnly: gosettings.CopyPointer(ss.SecureCoreOnly),
TorOnly: gosettings.CopyPointer(ss.TorOnly),
PortForwardOnly: gosettings.CopyPointer(ss.PortForwardOnly), PortForwardOnly: gosettings.CopyPointer(ss.PortForwardOnly),
MultiHopOnly: gosettings.CopyPointer(ss.MultiHopOnly), MultiHopOnly: gosettings.CopyPointer(ss.MultiHopOnly),
OpenVPN: ss.OpenVPN.copy(), OpenVPN: ss.OpenVPN.copy(),
@@ -321,8 +257,30 @@ func (ss *ServerSelection) copy() (copied ServerSelection) {
} }
} }
func (ss *ServerSelection) mergeWith(other ServerSelection) {
ss.VPN = gosettings.MergeWithString(ss.VPN, other.VPN)
ss.TargetIP = gosettings.MergeWithValidator(ss.TargetIP, other.TargetIP)
ss.Countries = gosettings.MergeWithSlice(ss.Countries, other.Countries)
ss.Categories = gosettings.MergeWithSlice(ss.Categories, other.Categories)
ss.Regions = gosettings.MergeWithSlice(ss.Regions, other.Regions)
ss.Cities = gosettings.MergeWithSlice(ss.Cities, other.Cities)
ss.ISPs = gosettings.MergeWithSlice(ss.ISPs, other.ISPs)
ss.Hostnames = gosettings.MergeWithSlice(ss.Hostnames, other.Hostnames)
ss.Names = gosettings.MergeWithSlice(ss.Names, other.Names)
ss.Numbers = gosettings.MergeWithSlice(ss.Numbers, other.Numbers)
ss.OwnedOnly = gosettings.MergeWithPointer(ss.OwnedOnly, other.OwnedOnly)
ss.FreeOnly = gosettings.MergeWithPointer(ss.FreeOnly, other.FreeOnly)
ss.PremiumOnly = gosettings.MergeWithPointer(ss.PremiumOnly, other.PremiumOnly)
ss.StreamOnly = gosettings.MergeWithPointer(ss.StreamOnly, other.StreamOnly)
ss.MultiHopOnly = gosettings.MergeWithPointer(ss.MultiHopOnly, other.MultiHopOnly)
ss.PortForwardOnly = gosettings.MergeWithPointer(ss.PortForwardOnly, other.PortForwardOnly)
ss.OpenVPN.mergeWith(other.OpenVPN)
ss.Wireguard.mergeWith(other.Wireguard)
}
func (ss *ServerSelection) overrideWith(other ServerSelection) { func (ss *ServerSelection) overrideWith(other ServerSelection) {
ss.VPN = gosettings.OverrideWithComparable(ss.VPN, other.VPN) ss.VPN = gosettings.OverrideWithString(ss.VPN, other.VPN)
ss.TargetIP = gosettings.OverrideWithValidator(ss.TargetIP, other.TargetIP) ss.TargetIP = gosettings.OverrideWithValidator(ss.TargetIP, other.TargetIP)
ss.Countries = gosettings.OverrideWithSlice(ss.Countries, other.Countries) ss.Countries = gosettings.OverrideWithSlice(ss.Countries, other.Countries)
ss.Categories = gosettings.OverrideWithSlice(ss.Categories, other.Categories) ss.Categories = gosettings.OverrideWithSlice(ss.Categories, other.Categories)
@@ -336,30 +294,21 @@ func (ss *ServerSelection) overrideWith(other ServerSelection) {
ss.FreeOnly = gosettings.OverrideWithPointer(ss.FreeOnly, other.FreeOnly) ss.FreeOnly = gosettings.OverrideWithPointer(ss.FreeOnly, other.FreeOnly)
ss.PremiumOnly = gosettings.OverrideWithPointer(ss.PremiumOnly, other.PremiumOnly) ss.PremiumOnly = gosettings.OverrideWithPointer(ss.PremiumOnly, other.PremiumOnly)
ss.StreamOnly = gosettings.OverrideWithPointer(ss.StreamOnly, other.StreamOnly) ss.StreamOnly = gosettings.OverrideWithPointer(ss.StreamOnly, other.StreamOnly)
ss.SecureCoreOnly = gosettings.OverrideWithPointer(ss.SecureCoreOnly, other.SecureCoreOnly)
ss.TorOnly = gosettings.OverrideWithPointer(ss.TorOnly, other.TorOnly)
ss.MultiHopOnly = gosettings.OverrideWithPointer(ss.MultiHopOnly, other.MultiHopOnly) ss.MultiHopOnly = gosettings.OverrideWithPointer(ss.MultiHopOnly, other.MultiHopOnly)
ss.PortForwardOnly = gosettings.OverrideWithPointer(ss.PortForwardOnly, other.PortForwardOnly) 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, portForwardingEnabled bool) { func (ss *ServerSelection) setDefaults(vpnProvider string) {
ss.VPN = gosettings.DefaultComparable(ss.VPN, vpn.OpenVPN) ss.VPN = gosettings.DefaultString(ss.VPN, vpn.OpenVPN)
ss.TargetIP = gosettings.DefaultValidator(ss.TargetIP, netip.IPv4Unspecified()) ss.TargetIP = gosettings.DefaultValidator(ss.TargetIP, netip.IPv4Unspecified())
ss.OwnedOnly = gosettings.DefaultPointer(ss.OwnedOnly, false) ss.OwnedOnly = gosettings.DefaultPointer(ss.OwnedOnly, false)
ss.FreeOnly = gosettings.DefaultPointer(ss.FreeOnly, false) ss.FreeOnly = gosettings.DefaultPointer(ss.FreeOnly, false)
ss.PremiumOnly = gosettings.DefaultPointer(ss.PremiumOnly, false) ss.PremiumOnly = gosettings.DefaultPointer(ss.PremiumOnly, false)
ss.StreamOnly = gosettings.DefaultPointer(ss.StreamOnly, false) ss.StreamOnly = gosettings.DefaultPointer(ss.StreamOnly, false)
ss.SecureCoreOnly = gosettings.DefaultPointer(ss.SecureCoreOnly, false)
ss.TorOnly = gosettings.DefaultPointer(ss.TorOnly, false)
ss.MultiHopOnly = gosettings.DefaultPointer(ss.MultiHopOnly, false) ss.MultiHopOnly = gosettings.DefaultPointer(ss.MultiHopOnly, false)
defaultPortForwardOnly := false ss.PortForwardOnly = gosettings.DefaultPointer(ss.PortForwardOnly, false)
if portForwardingEnabled && helpers.IsOneOf(vpnProvider,
providers.PrivateInternetAccess, providers.Protonvpn) {
defaultPortForwardOnly = true
}
ss.PortForwardOnly = gosettings.DefaultPointer(ss.PortForwardOnly, defaultPortForwardOnly)
ss.OpenVPN.setDefaults(vpnProvider) ss.OpenVPN.setDefaults(vpnProvider)
ss.Wireguard.setDefaults() ss.Wireguard.setDefaults()
} }
@@ -426,14 +375,6 @@ func (ss ServerSelection) toLinesNode() (node *gotree.Node) {
node.Appendf("Stream only servers: yes") node.Appendf("Stream only servers: yes")
} }
if *ss.SecureCoreOnly {
node.Appendf("Secure Core only servers: yes")
}
if *ss.TorOnly {
node.Appendf("Tor only servers: yes")
}
if *ss.MultiHopOnly { if *ss.MultiHopOnly {
node.Appendf("Multi-hop only servers: yes") node.Appendf("Multi-hop only servers: yes")
} }
@@ -454,96 +395,6 @@ func (ss ServerSelection) toLinesNode() (node *gotree.Node) {
// WithDefaults is a shorthand using setDefaults. // WithDefaults is a shorthand using setDefaults.
// It's used in unit tests in other packages. // It's used in unit tests in other packages.
func (ss ServerSelection) WithDefaults(provider string) ServerSelection { func (ss ServerSelection) WithDefaults(provider string) ServerSelection {
const portForwardingEnabled = false ss.setDefaults(provider)
ss.setDefaults(provider, portForwardingEnabled)
return ss return ss
} }
func (ss *ServerSelection) read(r *reader.Reader,
vpnProvider, vpnType string,
) (err error) {
ss.VPN = vpnType
ss.TargetIP, err = r.NetipAddr("OPENVPN_ENDPOINT_IP",
reader.RetroKeys("OPENVPN_TARGET_IP", "VPN_ENDPOINT_IP"))
if err != nil {
return err
}
countriesRetroKeys := []string{"COUNTRY"}
if vpnProvider == providers.Cyberghost {
countriesRetroKeys = append(countriesRetroKeys, "REGION")
}
ss.Countries = r.CSV("SERVER_COUNTRIES", reader.RetroKeys(countriesRetroKeys...))
ss.Regions = r.CSV("SERVER_REGIONS", reader.RetroKeys("REGION"))
ss.Cities = r.CSV("SERVER_CITIES", reader.RetroKeys("CITY"))
ss.ISPs = r.CSV("ISP")
ss.Hostnames = r.CSV("SERVER_HOSTNAMES", reader.RetroKeys("SERVER_HOSTNAME"))
ss.Names = r.CSV("SERVER_NAMES", reader.RetroKeys("SERVER_NAME"))
ss.Numbers, err = r.CSVUint16("SERVER_NUMBER")
ss.Categories = r.CSV("SERVER_CATEGORIES")
if err != nil {
return err
}
// Mullvad only
ss.OwnedOnly, err = r.BoolPtr("OWNED_ONLY", reader.RetroKeys("OWNED"))
if err != nil {
return err
}
// VPNUnlimited and ProtonVPN only
ss.FreeOnly, err = r.BoolPtr("FREE_ONLY")
if err != nil {
return err
}
// VPNSecure only
ss.PremiumOnly, err = r.BoolPtr("PREMIUM_ONLY")
if err != nil {
return err
}
// Surfshark only
ss.MultiHopOnly, err = r.BoolPtr("MULTIHOP_ONLY")
if err != nil {
return err
}
// VPNUnlimited and ProtonVPN only
ss.StreamOnly, err = r.BoolPtr("STREAM_ONLY")
if err != nil {
return err
}
// ProtonVPN only
ss.SecureCoreOnly, err = r.BoolPtr("SECURE_CORE_ONLY")
if err != nil {
return err
}
// ProtonVPN only
ss.TorOnly, err = r.BoolPtr("TOR_ONLY")
if err != nil {
return err
}
// PIA and ProtonVPN only
ss.PortForwardOnly, err = r.BoolPtr("PORT_FORWARD_ONLY")
if err != nil {
return err
}
err = ss.OpenVPN.read(r)
if err != nil {
return err
}
err = ss.Wireguard.read(r)
if err != nil {
return err
}
return nil
}

View File

@@ -2,14 +2,12 @@ package settings
import ( import (
"fmt" "fmt"
"net/netip"
"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/providers"
"github.com/qdm12/gluetun/internal/constants/vpn" "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/gluetun/internal/pprof"
"github.com/qdm12/gosettings/reader"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
) )
@@ -22,7 +20,6 @@ type Settings struct {
Log Log Log Log
PublicIP PublicIP PublicIP PublicIP
Shadowsocks Shadowsocks Shadowsocks Shadowsocks
Storage Storage
System System System System
Updater Updater Updater Updater
Version Version Version Version
@@ -30,16 +27,14 @@ type Settings struct {
Pprof pprof.Settings Pprof pprof.Settings
} }
type FilterChoicesGetter interface { type Storage interface {
GetFilterChoices(provider string) models.FilterChoices 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(filterChoicesGetter FilterChoicesGetter, ipv6Supported bool, func (s *Settings) Validate(storage Storage, ipv6Supported bool) (err error) {
warner Warner,
) (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,
@@ -49,13 +44,12 @@ func (s *Settings) Validate(filterChoicesGetter FilterChoicesGetter, ipv6Support
"log": s.Log.validate, "log": s.Log.validate,
"public ip check": s.PublicIP.validate, "public ip check": s.PublicIP.validate,
"shadowsocks": s.Shadowsocks.validate, "shadowsocks": s.Shadowsocks.validate,
"storage": s.Storage.validate,
"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 // Pprof validation done in pprof constructor
"VPN": func() error { "VPN": func() error {
return s.VPN.Validate(filterChoicesGetter, ipv6Supported, warner) return s.VPN.Validate(storage, ipv6Supported)
}, },
} }
@@ -79,7 +73,6 @@ func (s *Settings) copy() (copied Settings) {
Log: s.Log.copy(), Log: s.Log.copy(),
PublicIP: s.PublicIP.copy(), PublicIP: s.PublicIP.copy(),
Shadowsocks: s.Shadowsocks.copy(), Shadowsocks: s.Shadowsocks.copy(),
Storage: s.Storage.copy(),
System: s.System.copy(), System: s.System.copy(),
Updater: s.Updater.copy(), Updater: s.Updater.copy(),
Version: s.Version.copy(), Version: s.Version.copy(),
@@ -88,9 +81,24 @@ func (s *Settings) copy() (copied Settings) {
} }
} }
func (s *Settings) MergeWith(other Settings) {
s.ControlServer.mergeWith(other.ControlServer)
s.DNS.mergeWith(other.DNS)
s.Firewall.mergeWith(other.Firewall)
s.Health.MergeWith(other.Health)
s.HTTPProxy.mergeWith(other.HTTPProxy)
s.Log.mergeWith(other.Log)
s.PublicIP.mergeWith(other.PublicIP)
s.Shadowsocks.mergeWith(other.Shadowsocks)
s.System.mergeWith(other.System)
s.Updater.mergeWith(other.Updater)
s.Version.mergeWith(other.Version)
s.VPN.mergeWith(other.VPN)
s.Pprof.MergeWith(other.Pprof)
}
func (s *Settings) OverrideWith(other Settings, func (s *Settings) OverrideWith(other Settings,
filterChoicesGetter FilterChoicesGetter, ipv6Supported bool, warner Warner, storage Storage, ipv6Supported bool) (err error) {
) (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)
@@ -100,13 +108,12 @@ func (s *Settings) OverrideWith(other Settings,
patchedSettings.Log.overrideWith(other.Log) patchedSettings.Log.overrideWith(other.Log)
patchedSettings.PublicIP.overrideWith(other.PublicIP) patchedSettings.PublicIP.overrideWith(other.PublicIP)
patchedSettings.Shadowsocks.overrideWith(other.Shadowsocks) patchedSettings.Shadowsocks.overrideWith(other.Shadowsocks)
patchedSettings.Storage.overrideWith(other.Storage)
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) patchedSettings.Pprof.OverrideWith(other.Pprof)
err = patchedSettings.Validate(filterChoicesGetter, ipv6Supported, warner) err = patchedSettings.Validate(storage, ipv6Supported)
if err != nil { if err != nil {
return err return err
} }
@@ -123,11 +130,10 @@ func (s *Settings) SetDefaults() {
s.Log.setDefaults() s.Log.setDefaults()
s.PublicIP.setDefaults() s.PublicIP.setDefaults()
s.Shadowsocks.setDefaults() s.Shadowsocks.setDefaults()
s.Storage.setDefaults()
s.System.setDefaults() s.System.setDefaults()
s.Version.setDefaults() s.Version.setDefaults()
s.VPN.setDefaults() s.VPN.setDefaults()
s.Updater.SetDefaults(s.VPN.Provider.Name) s.Updater.SetDefaults(*s.VPN.Provider.Name)
s.Pprof.SetDefaults() s.Pprof.SetDefaults()
} }
@@ -146,7 +152,6 @@ func (s Settings) toLinesNode() (node *gotree.Node) {
node.AppendNode(s.Shadowsocks.toLinesNode()) node.AppendNode(s.Shadowsocks.toLinesNode())
node.AppendNode(s.HTTPProxy.toLinesNode()) node.AppendNode(s.HTTPProxy.toLinesNode())
node.AppendNode(s.ControlServer.toLinesNode()) node.AppendNode(s.ControlServer.toLinesNode())
node.AppendNode(s.Storage.toLinesNode())
node.AppendNode(s.System.toLinesNode()) node.AppendNode(s.System.toLinesNode())
node.AppendNode(s.PublicIP.toLinesNode()) node.AppendNode(s.PublicIP.toLinesNode())
node.AppendNode(s.Updater.toLinesNode()) node.AppendNode(s.Updater.toLinesNode())
@@ -157,16 +162,16 @@ func (s Settings) toLinesNode() (node *gotree.Node) {
} }
func (s Settings) Warnings() (warnings []string) { func (s Settings) Warnings() (warnings []string) {
if s.VPN.Provider.Name == providers.HideMyAss { if *s.VPN.Provider.Name == providers.HideMyAss {
warnings = append(warnings, "HideMyAss dropped support for Linux OpenVPN "+ warnings = append(warnings, "HideMyAss dropped support for Linux OpenVPN "+
" so this will likely not work anymore. See https://github.com/qdm12/gluetun/issues/1498.") " so this will likely not work anymore. See https://github.com/qdm12/gluetun/issues/1498.")
} }
if helpers.IsOneOf(s.VPN.Provider.Name, providers.SlickVPN) && if helpers.IsOneOf(*s.VPN.Provider.Name, providers.SlickVPN) &&
s.VPN.Type == vpn.OpenVPN { s.VPN.Type == vpn.OpenVPN {
warnings = append(warnings, "OpenVPN 2.5 and 2.6 use OpenSSL 3 "+ warnings = append(warnings, "OpenVPN 2.5 uses OpenSSL 3 "+
"which prohibits the usage of weak security in today's standards. "+ "which prohibits the usage of weak security in today's standards. "+
s.VPN.Provider.Name+" uses weak security which is out "+ *s.VPN.Provider.Name+" uses weak security which is out "+
"of Gluetun's control so the only workaround is to allow such weaknesses "+ "of Gluetun's control so the only workaround is to allow such weaknesses "+
`using the OpenVPN option tls-cipher "DEFAULT:@SECLEVEL=0". `+ `using the OpenVPN option tls-cipher "DEFAULT:@SECLEVEL=0". `+
"You might want to reach to your provider so they upgrade their certificates. "+ "You might want to reach to your provider so they upgrade their certificates. "+
@@ -174,49 +179,5 @@ func (s Settings) Warnings() (warnings []string) {
"by creating an issue, attaching the new certificate and we will update Gluetun.") "by creating an issue, attaching the new certificate and we will update Gluetun.")
} }
// TODO remove in v4
if s.DNS.ServerAddress.Unmap().Compare(netip.AddrFrom4([4]byte{127, 0, 0, 1})) != 0 {
warnings = append(warnings, "DNS address is set to "+s.DNS.ServerAddress.String()+
" so the DNS over TLS (DoT) server will not be used."+
" The default value changed to 127.0.0.1 so it uses the internal DoT serves."+
" If the DoT server fails to start, the IPv4 address of the first plaintext DNS server"+
" corresponding to the first DoT provider chosen is used.")
}
return warnings return warnings
} }
func (s *Settings) Read(r *reader.Reader, warner Warner) (err error) {
warnings := readObsolete(r)
for _, warning := range warnings {
warner.Warn(warning)
}
readFunctions := map[string]func(r *reader.Reader) error{
"control server": s.ControlServer.read,
"DNS": s.DNS.read,
"firewall": s.Firewall.read,
"health": s.Health.Read,
"http proxy": s.HTTPProxy.read,
"log": s.Log.read,
"public ip": func(r *reader.Reader) error {
return s.PublicIP.read(r, warner)
},
"shadowsocks": s.Shadowsocks.read,
"storage": s.Storage.read,
"system": s.System.read,
"updater": s.Updater.read,
"version": s.Version.read,
"VPN": s.VPN.read,
"profiling": s.Pprof.Read,
}
for name, read := range readFunctions {
err = read(r)
if err != nil {
return fmt.Errorf("reading %s settings: %w", name, err)
}
}
return nil
}

View File

@@ -30,7 +30,7 @@ func Test_Settings_String(t *testing.T) {
| | ├── Protocol: UDP | | ├── Protocol: UDP
| | └── Private Internet Access encryption preset: strong | | └── Private Internet Access encryption preset: strong
| └── OpenVPN settings: | └── OpenVPN settings:
| ├── OpenVPN version: 2.6 | ├── OpenVPN version: 2.5
| ├── User: [not set] | ├── User: [not set]
| ├── Password: [not set] | ├── Password: [not set]
| ├── Private Internet Access encryption preset: strong | ├── Private Internet Access encryption preset: strong
@@ -43,10 +43,18 @@ func Test_Settings_String(t *testing.T) {
| └── DNS over TLS settings: | └── DNS over TLS settings:
| ├── Enabled: yes | ├── Enabled: yes
| ├── Update period: every 24h0m0s | ├── Update period: every 24h0m0s
| ├── Upstream resolvers: | ├── Unbound settings:
| | ── Cloudflare | | ── Authoritative servers:
| ├── Caching: yes | | | └── Cloudflare
| ├── IPv6: no | | ├── Caching: yes
| | ├── IPv6: no
| | ├── Verbosity level: 1
| | ├── Verbosity details level: 0
| | ├── Validation log level: 0
| | ├── System user: root
| | └── Allowed networks:
| | ├── 0.0.0.0/0
| | └── ::/0
| └── DNS filtering settings: | └── DNS filtering settings:
| ├── Block malicious: yes | ├── Block malicious: yes
| ├── Block ads: no | ├── Block ads: no
@@ -70,26 +78,21 @@ func Test_Settings_String(t *testing.T) {
| └── Enabled: no | └── Enabled: no
├── Control server settings: ├── Control server settings:
| ├── Listening address: :8000 | ├── Listening address: :8000
| ── Logging: yes | ── Logging: yes
| └── Authentication file path: /gluetun/auth/config.toml
├── Storage settings:
| └── Filepath: /gluetun/servers.json
├── 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
| ├── IP file path: /tmp/gluetun/ip | ├── IP file path: /tmp/gluetun/ip
| ── Public IP data base API: ipinfo | ── Public IP data API: ipinfo
| └── Public IP data backup APIs:
| ├── cloudflare
| ├── ifconfigco
| └── ip2location
└── Version settings: └── Version settings:
└── Enabled: yes`, └── Enabled: yes`,
}, },
} }
for name, testCase := range testCases { for name, testCase := range testCases {
testCase := testCase
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
t.Parallel() t.Parallel()

View File

@@ -1,10 +1,7 @@
package settings package settings
import ( import (
"fmt"
"github.com/qdm12/gosettings" "github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/reader"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
"github.com/qdm12/ss-server/pkg/tcpudp" "github.com/qdm12/ss-server/pkg/tcpudp"
) )
@@ -29,6 +26,13 @@ func (s *Shadowsocks) copy() (copied Shadowsocks) {
} }
} }
// mergeWith merges the other settings into any
// unset field of the receiver settings object.
func (s *Shadowsocks) mergeWith(other Shadowsocks) {
s.Enabled = gosettings.MergeWithPointer(s.Enabled, other.Enabled)
s.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.
@@ -55,44 +59,10 @@ func (s Shadowsocks) toLinesNode() (node *gotree.Node) {
} }
// TODO have ToLinesNode in qdm12/ss-server // TODO have ToLinesNode in qdm12/ss-server
node.Appendf("Listening address: %s", *s.Settings.Address) node.Appendf("Listening address: %s", *s.Address)
node.Appendf("Cipher: %s", s.Settings.CipherName) node.Appendf("Cipher: %s", s.CipherName)
node.Appendf("Password: %s", gosettings.ObfuscateKey(*s.Settings.Password)) node.Appendf("Password: %s", gosettings.ObfuscateKey(*s.Password))
node.Appendf("Log addresses: %s", gosettings.BoolToYesNo(s.Settings.LogAddresses)) node.Appendf("Log addresses: %s", gosettings.BoolToYesNo(s.LogAddresses))
return node return node
} }
func (s *Shadowsocks) read(r *reader.Reader) (err error) {
s.Enabled, err = r.BoolPtr("SHADOWSOCKS")
if err != nil {
return err
}
s.Settings.Address, err = readShadowsocksAddress(r)
if err != nil {
return err
}
s.Settings.LogAddresses, err = r.BoolPtr("SHADOWSOCKS_LOG")
if err != nil {
return err
}
s.Settings.CipherName = r.String("SHADOWSOCKS_CIPHER",
reader.RetroKeys("SHADOWSOCKS_METHOD"))
s.Settings.Password = r.Get("SHADOWSOCKS_PASSWORD",
reader.ForceLowercase(false))
return nil
}
func readShadowsocksAddress(r *reader.Reader) (address *string, err error) {
const currentKey = "SHADOWSOCKS_LISTENING_ADDRESS"
port, err := r.Uint16Ptr("SHADOWSOCKS_PORT", reader.IsRetro(currentKey)) // retro-compatibility
if err != nil {
return nil, err
} else if port != nil {
return ptrTo(fmt.Sprintf(":%d", *port)), nil
}
return r.Get(currentKey), nil
}

View File

@@ -1,59 +0,0 @@
package settings
import (
"fmt"
"path/filepath"
"github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/reader"
"github.com/qdm12/gotree"
)
// Storage contains settings to configure the storage.
type Storage struct {
// Filepath is the path to the servers.json file. An empty string disables on-disk storage.
Filepath *string
}
func (s Storage) validate() (err error) {
if *s.Filepath != "" { // optional
_, err := filepath.Abs(*s.Filepath)
if err != nil {
return fmt.Errorf("filepath is not valid: %w", err)
}
}
return nil
}
func (s *Storage) copy() (copied Storage) {
return Storage{
Filepath: gosettings.CopyPointer(s.Filepath),
}
}
func (s *Storage) overrideWith(other Storage) {
s.Filepath = gosettings.OverrideWithPointer(s.Filepath, other.Filepath)
}
func (s *Storage) setDefaults() {
const defaultFilepath = "/gluetun/servers.json"
s.Filepath = gosettings.DefaultPointer(s.Filepath, defaultFilepath)
}
func (s Storage) String() string {
return s.toLinesNode().String()
}
func (s Storage) toLinesNode() (node *gotree.Node) {
if *s.Filepath == "" {
return gotree.New("Storage settings: disabled")
}
node = gotree.New("Storage settings:")
node.Appendf("Filepath: %s", *s.Filepath)
return node
}
func (s *Storage) read(r *reader.Reader) (err error) {
s.Filepath = r.Get("STORAGE_FILEPATH", reader.AcceptEmpty(true))
return nil
}

View File

@@ -7,8 +7,7 @@ import (
) )
func surfsharkRetroRegion(selection ServerSelection) ( func surfsharkRetroRegion(selection ServerSelection) (
updatedSelection ServerSelection, updatedSelection ServerSelection) {
) {
locationData := servers.LocationData() locationData := servers.LocationData()
retroToLocation := make(map[string]servers.ServerLocation, len(locationData)) retroToLocation := make(map[string]servers.ServerLocation, len(locationData))

View File

@@ -2,7 +2,6 @@ package settings
import ( import (
"github.com/qdm12/gosettings" "github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/reader"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
) )
@@ -26,10 +25,16 @@ func (s *System) copy() (copied System) {
} }
} }
func (s *System) mergeWith(other System) {
s.PUID = gosettings.MergeWithPointer(s.PUID, other.PUID)
s.PGID = gosettings.MergeWithPointer(s.PGID, other.PGID)
s.Timezone = gosettings.MergeWithString(s.Timezone, other.Timezone)
}
func (s *System) overrideWith(other System) { func (s *System) overrideWith(other System) {
s.PUID = gosettings.OverrideWithPointer(s.PUID, other.PUID) s.PUID = gosettings.OverrideWithPointer(s.PUID, other.PUID)
s.PGID = gosettings.OverrideWithPointer(s.PGID, other.PGID) s.PGID = gosettings.OverrideWithPointer(s.PGID, other.PGID)
s.Timezone = gosettings.OverrideWithComparable(s.Timezone, other.Timezone) s.Timezone = gosettings.OverrideWithString(s.Timezone, other.Timezone)
} }
func (s *System) setDefaults() { func (s *System) setDefaults() {
@@ -54,18 +59,3 @@ func (s System) toLinesNode() (node *gotree.Node) {
return node return node
} }
func (s *System) read(r *reader.Reader) (err error) {
s.PUID, err = r.Uint32Ptr("PUID", reader.RetroKeys("UID"))
if err != nil {
return err
}
s.PGID, err = r.Uint32Ptr("PGID", reader.RetroKeys("GID"))
if err != nil {
return err
}
s.Timezone = r.String("TZ")
return nil
}

View File

@@ -0,0 +1,202 @@
package settings
import (
"errors"
"fmt"
"net/netip"
"github.com/qdm12/dns/pkg/provider"
"github.com/qdm12/dns/pkg/unbound"
"github.com/qdm12/gosettings"
"github.com/qdm12/gotree"
)
// Unbound is settings for the Unbound program.
type Unbound struct {
Providers []string `json:"providers"`
Caching *bool `json:"caching"`
IPv6 *bool `json:"ipv6"`
VerbosityLevel *uint8 `json:"verbosity_level"`
VerbosityDetailsLevel *uint8 `json:"verbosity_details_level"`
ValidationLogLevel *uint8 `json:"validation_log_level"`
Username string `json:"username"`
Allowed []netip.Prefix `json:"allowed"`
}
func (u *Unbound) setDefaults() {
if len(u.Providers) == 0 {
u.Providers = []string{
provider.Cloudflare().String(),
}
}
u.Caching = gosettings.DefaultPointer(u.Caching, true)
u.IPv6 = gosettings.DefaultPointer(u.IPv6, false)
const defaultVerbosityLevel = 1
u.VerbosityLevel = gosettings.DefaultPointer(u.VerbosityLevel, defaultVerbosityLevel)
const defaultVerbosityDetailsLevel = 0
u.VerbosityDetailsLevel = gosettings.DefaultPointer(u.VerbosityDetailsLevel, defaultVerbosityDetailsLevel)
const defaultValidationLogLevel = 0
u.ValidationLogLevel = gosettings.DefaultPointer(u.ValidationLogLevel, defaultValidationLogLevel)
if u.Allowed == nil {
u.Allowed = []netip.Prefix{
netip.PrefixFrom(netip.AddrFrom4([4]byte{}), 0),
netip.PrefixFrom(netip.AddrFrom16([16]byte{}), 0),
}
}
u.Username = gosettings.DefaultString(u.Username, "root")
}
var (
ErrUnboundVerbosityLevelNotValid = errors.New("Unbound verbosity level is not valid")
ErrUnboundVerbosityDetailsLevelNotValid = errors.New("Unbound verbosity details level is not valid")
ErrUnboundValidationLogLevelNotValid = errors.New("Unbound validation log level is not valid")
)
func (u Unbound) validate() (err error) {
for _, s := range u.Providers {
_, err := provider.Parse(s)
if err != nil {
return err
}
}
const maxVerbosityLevel = 5
if *u.VerbosityLevel > maxVerbosityLevel {
return fmt.Errorf("%w: %d must be between 0 and %d",
ErrUnboundVerbosityLevelNotValid,
*u.VerbosityLevel,
maxVerbosityLevel)
}
const maxVerbosityDetailsLevel = 4
if *u.VerbosityDetailsLevel > maxVerbosityDetailsLevel {
return fmt.Errorf("%w: %d must be between 0 and %d",
ErrUnboundVerbosityDetailsLevelNotValid,
*u.VerbosityDetailsLevel,
maxVerbosityDetailsLevel)
}
const maxValidationLogLevel = 2
if *u.ValidationLogLevel > maxValidationLogLevel {
return fmt.Errorf("%w: %d must be between 0 and %d",
ErrUnboundValidationLogLevelNotValid,
*u.ValidationLogLevel, maxValidationLogLevel)
}
return nil
}
func (u Unbound) copy() (copied Unbound) {
return Unbound{
Providers: gosettings.CopySlice(u.Providers),
Caching: gosettings.CopyPointer(u.Caching),
IPv6: gosettings.CopyPointer(u.IPv6),
VerbosityLevel: gosettings.CopyPointer(u.VerbosityLevel),
VerbosityDetailsLevel: gosettings.CopyPointer(u.VerbosityDetailsLevel),
ValidationLogLevel: gosettings.CopyPointer(u.ValidationLogLevel),
Username: u.Username,
Allowed: gosettings.CopySlice(u.Allowed),
}
}
func (u *Unbound) mergeWith(other Unbound) {
u.Providers = gosettings.MergeWithSlice(u.Providers, other.Providers)
u.Caching = gosettings.MergeWithPointer(u.Caching, other.Caching)
u.IPv6 = gosettings.MergeWithPointer(u.IPv6, other.IPv6)
u.VerbosityLevel = gosettings.MergeWithPointer(u.VerbosityLevel, other.VerbosityLevel)
u.VerbosityDetailsLevel = gosettings.MergeWithPointer(u.VerbosityDetailsLevel, other.VerbosityDetailsLevel)
u.ValidationLogLevel = gosettings.MergeWithPointer(u.ValidationLogLevel, other.ValidationLogLevel)
u.Username = gosettings.MergeWithString(u.Username, other.Username)
u.Allowed = gosettings.MergeWithSlice(u.Allowed, other.Allowed)
}
func (u *Unbound) overrideWith(other Unbound) {
u.Providers = gosettings.OverrideWithSlice(u.Providers, other.Providers)
u.Caching = gosettings.OverrideWithPointer(u.Caching, other.Caching)
u.IPv6 = gosettings.OverrideWithPointer(u.IPv6, other.IPv6)
u.VerbosityLevel = gosettings.OverrideWithPointer(u.VerbosityLevel, other.VerbosityLevel)
u.VerbosityDetailsLevel = gosettings.OverrideWithPointer(u.VerbosityDetailsLevel, other.VerbosityDetailsLevel)
u.ValidationLogLevel = gosettings.OverrideWithPointer(u.ValidationLogLevel, other.ValidationLogLevel)
u.Username = gosettings.OverrideWithString(u.Username, other.Username)
u.Allowed = gosettings.OverrideWithSlice(u.Allowed, other.Allowed)
}
func (u Unbound) ToUnboundFormat() (settings unbound.Settings, err error) {
providers := make([]provider.Provider, len(u.Providers))
for i := range providers {
providers[i], err = provider.Parse(u.Providers[i])
if err != nil {
return settings, err
}
}
const port = 53
return unbound.Settings{
ListeningPort: port,
IPv4: true,
Providers: providers,
Caching: *u.Caching,
IPv6: *u.IPv6,
VerbosityLevel: *u.VerbosityLevel,
VerbosityDetailsLevel: *u.VerbosityDetailsLevel,
ValidationLogLevel: *u.ValidationLogLevel,
AccessControl: unbound.AccessControlSettings{
Allowed: netipPrefixesToNetaddrIPPrefixes(u.Allowed),
},
Username: u.Username,
}, nil
}
var (
ErrConvertingNetip = errors.New("converting net.IP to netip.Addr failed")
)
func (u Unbound) GetFirstPlaintextIPv4() (ipv4 netip.Addr, err error) {
s := u.Providers[0]
provider, err := provider.Parse(s)
if err != nil {
return ipv4, err
}
ip := provider.DNS().IPv4[0]
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 {
return u.toLinesNode().String()
}
func (u Unbound) toLinesNode() (node *gotree.Node) {
node = gotree.New("Unbound settings:")
authServers := node.Appendf("Authoritative servers:")
for _, provider := range u.Providers {
authServers.Appendf(provider)
}
node.Appendf("Caching: %s", gosettings.BoolToYesNo(u.Caching))
node.Appendf("IPv6: %s", gosettings.BoolToYesNo(u.IPv6))
node.Appendf("Verbosity level: %d", *u.VerbosityLevel)
node.Appendf("Verbosity details level: %d", *u.VerbosityDetailsLevel)
node.Appendf("Validation log level: %d", *u.ValidationLogLevel)
node.Appendf("System user: %s", u.Username)
allowedNetworks := node.Appendf("Allowed networks:")
for _, network := range u.Allowed {
allowedNetworks.Appendf(network.String())
}
return node
}

View File

@@ -0,0 +1,43 @@
package settings
import (
"encoding/json"
"net/netip"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_Unbound_JSON(t *testing.T) {
t.Parallel()
settings := Unbound{
Providers: []string{"cloudflare"},
Caching: boolPtr(true),
IPv6: boolPtr(false),
VerbosityLevel: uint8Ptr(1),
VerbosityDetailsLevel: nil,
ValidationLogLevel: uint8Ptr(0),
Username: "user",
Allowed: []netip.Prefix{
netip.PrefixFrom(netip.AddrFrom4([4]byte{}), 0),
netip.PrefixFrom(netip.AddrFrom16([16]byte{}), 0),
},
}
b, err := json.Marshal(settings)
require.NoError(t, err)
const expected = `{"providers":["cloudflare"],"caching":true,"ipv6":false,` +
`"verbosity_level":1,"verbosity_details_level":null,"validation_log_level":0,` +
`"username":"user","allowed":["0.0.0.0/0","::/0"]}`
assert.Equal(t, expected, string(b))
var resultSettings Unbound
err = json.Unmarshal(b, &resultSettings)
require.NoError(t, err)
assert.Equal(t, settings, resultSettings)
}

View File

@@ -7,7 +7,6 @@ import (
"github.com/qdm12/gluetun/internal/constants/providers" "github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gosettings" "github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/reader"
"github.com/qdm12/gosettings/validate" "github.com/qdm12/gosettings/validate"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
) )
@@ -65,19 +64,28 @@ func (u *Updater) copy() (copied Updater) {
} }
} }
// mergeWith merges the other settings into any
// unset field of the receiver settings object.
func (u *Updater) mergeWith(other Updater) {
u.Period = gosettings.MergeWithPointer(u.Period, other.Period)
u.DNSAddress = gosettings.MergeWithString(u.DNSAddress, other.DNSAddress)
u.MinRatio = gosettings.MergeWithNumber(u.MinRatio, other.MinRatio)
u.Providers = gosettings.MergeWithSlice(u.Providers, other.Providers)
}
// overrideWith overrides fields of the receiver // 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 = gosettings.OverrideWithPointer(u.Period, other.Period)
u.DNSAddress = gosettings.OverrideWithComparable(u.DNSAddress, other.DNSAddress) u.DNSAddress = gosettings.OverrideWithString(u.DNSAddress, other.DNSAddress)
u.MinRatio = gosettings.OverrideWithComparable(u.MinRatio, other.MinRatio) u.MinRatio = gosettings.OverrideWithNumber(u.MinRatio, other.MinRatio)
u.Providers = gosettings.OverrideWithSlice(u.Providers, other.Providers) u.Providers = gosettings.OverrideWithSlice(u.Providers, other.Providers)
} }
func (u *Updater) SetDefaults(vpnProvider string) { func (u *Updater) SetDefaults(vpnProvider string) {
u.Period = gosettings.DefaultPointer(u.Period, 0) u.Period = gosettings.DefaultPointer(u.Period, 0)
u.DNSAddress = gosettings.DefaultComparable(u.DNSAddress, "1.1.1.1:53") u.DNSAddress = gosettings.DefaultString(u.DNSAddress, "1.1.1.1:53")
if u.MinRatio == 0 { if u.MinRatio == 0 {
const defaultMinRatio = 0.8 const defaultMinRatio = 0.8
@@ -106,33 +114,3 @@ func (u Updater) toLinesNode() (node *gotree.Node) {
return node return node
} }
func (u *Updater) read(r *reader.Reader) (err error) {
u.Period, err = r.DurationPtr("UPDATER_PERIOD")
if err != nil {
return err
}
u.DNSAddress, err = readUpdaterDNSAddress()
if err != nil {
return err
}
u.MinRatio, err = r.Float64("UPDATER_MIN_RATIO")
if err != nil {
return err
}
u.Providers = r.CSV("UPDATER_VPN_SERVICE_PROVIDERS")
return nil
}
func readUpdaterDNSAddress() (address string, err error) {
// TODO this is currently using Cloudflare in
// plaintext to not be blocked by DNS over TLS by default.
// If a plaintext address is set in the DNS settings, this one will be used.
// use custom future encrypted DNS written in Go without blocking
// as it's too much trouble to start another parallel unbound instance for now.
return "", nil
}

View File

@@ -2,7 +2,6 @@ package settings
import ( import (
"github.com/qdm12/gosettings" "github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/reader"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
) )
@@ -24,6 +23,12 @@ func (v *Version) copy() (copied Version) {
} }
} }
// mergeWith merges the other settings into any
// unset field of the receiver settings object.
func (v *Version) mergeWith(other Version) {
v.Enabled = gosettings.MergeWithPointer(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.
@@ -46,12 +51,3 @@ func (v Version) toLinesNode() (node *gotree.Node) {
return node return node
} }
func (v *Version) read(r *reader.Reader) (err error) {
v.Enabled, err = r.BoolPtr("VERSION_INFORMATION")
if err != nil {
return err
}
return nil
}

View File

@@ -5,7 +5,6 @@ import (
"github.com/qdm12/gluetun/internal/constants/vpn" "github.com/qdm12/gluetun/internal/constants/vpn"
"github.com/qdm12/gosettings" "github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/reader"
"github.com/qdm12/gosettings/validate" "github.com/qdm12/gosettings/validate"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
) )
@@ -21,25 +20,25 @@ type VPN struct {
} }
// TODO v4 remove pointer for receiver (because of Surfshark). // TODO v4 remove pointer for receiver (because of Surfshark).
func (v *VPN) Validate(filterChoicesGetter FilterChoicesGetter, ipv6Supported bool, warner Warner) (err error) { func (v *VPN) Validate(storage Storage, ipv6Supported bool) (err error) {
// Validate Type // Validate Type
validVPNTypes := []string{vpn.OpenVPN, vpn.Wireguard} validVPNTypes := []string{vpn.OpenVPN, vpn.Wireguard}
if err = validate.IsOneOf(v.Type, validVPNTypes...); err != nil { if err = validate.IsOneOf(v.Type, validVPNTypes...); err != nil {
return fmt.Errorf("%w: %w", ErrVPNTypeNotValid, err) return fmt.Errorf("%w: %w", ErrVPNTypeNotValid, err)
} }
err = v.Provider.validate(v.Type, filterChoicesGetter, warner) err = v.Provider.validate(v.Type, storage)
if err != nil { if err != nil {
return fmt.Errorf("provider settings: %w", err) return fmt.Errorf("provider settings: %w", err)
} }
if v.Type == vpn.OpenVPN { if v.Type == vpn.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: %w", err)
} }
} else { } else {
err := v.Wireguard.validate(v.Provider.Name, ipv6Supported) err := v.Wireguard.validate(*v.Provider.Name, ipv6Supported)
if err != nil { if err != nil {
return fmt.Errorf("Wireguard settings: %w", err) return fmt.Errorf("Wireguard settings: %w", err)
} }
@@ -57,18 +56,25 @@ func (v *VPN) Copy() (copied VPN) {
} }
} }
func (v *VPN) mergeWith(other VPN) {
v.Type = gosettings.MergeWithString(v.Type, other.Type)
v.Provider.mergeWith(other.Provider)
v.OpenVPN.mergeWith(other.OpenVPN)
v.Wireguard.mergeWith(other.Wireguard)
}
func (v *VPN) OverrideWith(other VPN) { func (v *VPN) OverrideWith(other VPN) {
v.Type = gosettings.OverrideWithComparable(v.Type, other.Type) v.Type = gosettings.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.DefaultComparable(v.Type, vpn.OpenVPN) v.Type = gosettings.DefaultString(v.Type, vpn.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(*v.Provider.Name)
} }
func (v VPN) String() string { func (v VPN) String() string {
@@ -88,24 +94,3 @@ func (v VPN) toLinesNode() (node *gotree.Node) {
return node return node
} }
func (v *VPN) read(r *reader.Reader) (err error) {
v.Type = r.String("VPN_TYPE")
err = v.Provider.read(r, v.Type)
if err != nil {
return fmt.Errorf("VPN provider: %w", err)
}
err = v.OpenVPN.read(r)
if err != nil {
return fmt.Errorf("OpenVPN: %w", err)
}
err = v.Wireguard.read(r)
if err != nil {
return fmt.Errorf("wireguard: %w", err)
}
return nil
}

View File

@@ -4,13 +4,10 @@ import (
"fmt" "fmt"
"net/netip" "net/netip"
"regexp" "regexp"
"strings"
"time"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers" "github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/constants/providers" "github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gosettings" "github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/reader"
"github.com/qdm12/gosettings/validate" "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"
@@ -36,7 +33,6 @@ type Wireguard struct {
// 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 `json:"interface"`
PersistentKeepaliveInterval *time.Duration `json:"persistent_keep_alive_interval"`
// Maximum Transmission Unit (MTU) of the Wireguard interface. // Maximum Transmission Unit (MTU) of the Wireguard interface.
// It cannot be zero in the internal state, and defaults to // It cannot be zero in the internal state, and defaults to
// 1400. Note it is not the wireguard-go MTU default of 1420 // 1400. Note it is not the wireguard-go MTU default of 1420
@@ -58,11 +54,9 @@ func (w Wireguard) validate(vpnProvider string, ipv6Supported bool) (err error)
if !helpers.IsOneOf(vpnProvider, if !helpers.IsOneOf(vpnProvider,
providers.Airvpn, providers.Airvpn,
providers.Custom, providers.Custom,
providers.Fastestvpn,
providers.Ivpn, providers.Ivpn,
providers.Mullvad, providers.Mullvad,
providers.Nordvpn, providers.Nordvpn,
providers.Protonvpn,
providers.Surfshark, providers.Surfshark,
providers.Windscribe, providers.Windscribe,
) { ) {
@@ -127,11 +121,6 @@ func (w Wireguard) validate(vpnProvider string, ipv6Supported bool) (err error)
} }
} }
if *w.PersistentKeepaliveInterval < 0 {
return fmt.Errorf("%w: %s", ErrWireguardKeepAliveNegative,
*w.PersistentKeepaliveInterval)
}
// Validate interface // Validate interface
if !regexpInterfaceName.MatchString(w.Interface) { if !regexpInterfaceName.MatchString(w.Interface) {
return fmt.Errorf("%w: '%s' does not match regex '%s'", return fmt.Errorf("%w: '%s' does not match regex '%s'",
@@ -152,48 +141,49 @@ func (w *Wireguard) copy() (copied Wireguard) {
PreSharedKey: gosettings.CopyPointer(w.PreSharedKey), PreSharedKey: gosettings.CopyPointer(w.PreSharedKey),
Addresses: gosettings.CopySlice(w.Addresses), Addresses: gosettings.CopySlice(w.Addresses),
AllowedIPs: gosettings.CopySlice(w.AllowedIPs), AllowedIPs: gosettings.CopySlice(w.AllowedIPs),
PersistentKeepaliveInterval: gosettings.CopyPointer(w.PersistentKeepaliveInterval),
Interface: w.Interface, Interface: w.Interface,
MTU: w.MTU, MTU: w.MTU,
Implementation: w.Implementation, Implementation: w.Implementation,
} }
} }
func (w *Wireguard) mergeWith(other Wireguard) {
w.PrivateKey = gosettings.MergeWithPointer(w.PrivateKey, other.PrivateKey)
w.PreSharedKey = gosettings.MergeWithPointer(w.PreSharedKey, other.PreSharedKey)
w.Addresses = gosettings.MergeWithSlice(w.Addresses, other.Addresses)
w.AllowedIPs = gosettings.MergeWithSlice(w.AllowedIPs, other.AllowedIPs)
w.Interface = gosettings.MergeWithString(w.Interface, other.Interface)
w.MTU = gosettings.MergeWithNumber(w.MTU, other.MTU)
w.Implementation = gosettings.MergeWithString(w.Implementation, other.Implementation)
}
func (w *Wireguard) overrideWith(other Wireguard) { func (w *Wireguard) overrideWith(other Wireguard) {
w.PrivateKey = gosettings.OverrideWithPointer(w.PrivateKey, other.PrivateKey) w.PrivateKey = gosettings.OverrideWithPointer(w.PrivateKey, other.PrivateKey)
w.PreSharedKey = gosettings.OverrideWithPointer(w.PreSharedKey, other.PreSharedKey) w.PreSharedKey = gosettings.OverrideWithPointer(w.PreSharedKey, other.PreSharedKey)
w.Addresses = gosettings.OverrideWithSlice(w.Addresses, other.Addresses) w.Addresses = gosettings.OverrideWithSlice(w.Addresses, other.Addresses)
w.AllowedIPs = gosettings.OverrideWithSlice(w.AllowedIPs, other.AllowedIPs) w.AllowedIPs = gosettings.OverrideWithSlice(w.AllowedIPs, other.AllowedIPs)
w.PersistentKeepaliveInterval = gosettings.OverrideWithPointer(w.PersistentKeepaliveInterval, w.Interface = gosettings.OverrideWithString(w.Interface, other.Interface)
other.PersistentKeepaliveInterval) w.MTU = gosettings.OverrideWithNumber(w.MTU, other.MTU)
w.Interface = gosettings.OverrideWithComparable(w.Interface, other.Interface) w.Implementation = gosettings.OverrideWithString(w.Implementation, other.Implementation)
w.MTU = gosettings.OverrideWithComparable(w.MTU, other.MTU)
w.Implementation = gosettings.OverrideWithComparable(w.Implementation, other.Implementation)
} }
func (w *Wireguard) setDefaults(vpnProvider string) { func (w *Wireguard) setDefaults(vpnProvider string) {
w.PrivateKey = gosettings.DefaultPointer(w.PrivateKey, "") w.PrivateKey = gosettings.DefaultPointer(w.PrivateKey, "")
w.PreSharedKey = gosettings.DefaultPointer(w.PreSharedKey, "") w.PreSharedKey = gosettings.DefaultPointer(w.PreSharedKey, "")
switch vpnProvider { if vpnProvider == providers.Nordvpn {
case providers.Nordvpn:
defaultNordVPNAddress := netip.AddrFrom4([4]byte{10, 5, 0, 2}) defaultNordVPNAddress := netip.AddrFrom4([4]byte{10, 5, 0, 2})
defaultNordVPNPrefix := netip.PrefixFrom(defaultNordVPNAddress, defaultNordVPNAddress.BitLen()) defaultNordVPNPrefix := netip.PrefixFrom(defaultNordVPNAddress, defaultNordVPNAddress.BitLen())
w.Addresses = gosettings.DefaultSlice(w.Addresses, []netip.Prefix{defaultNordVPNPrefix}) w.Addresses = gosettings.DefaultSlice(w.Addresses, []netip.Prefix{defaultNordVPNPrefix})
case providers.Protonvpn:
defaultAddress := netip.AddrFrom4([4]byte{10, 2, 0, 2})
defaultPrefix := netip.PrefixFrom(defaultAddress, defaultAddress.BitLen())
w.Addresses = gosettings.DefaultSlice(w.Addresses, []netip.Prefix{defaultPrefix})
} }
defaultAllowedIPs := []netip.Prefix{ defaultAllowedIPs := []netip.Prefix{
netip.PrefixFrom(netip.IPv4Unspecified(), 0), netip.PrefixFrom(netip.IPv4Unspecified(), 0),
netip.PrefixFrom(netip.IPv6Unspecified(), 0), netip.PrefixFrom(netip.IPv6Unspecified(), 0),
} }
w.AllowedIPs = gosettings.DefaultSlice(w.AllowedIPs, defaultAllowedIPs) w.AllowedIPs = gosettings.DefaultSlice(w.AllowedIPs, defaultAllowedIPs)
w.PersistentKeepaliveInterval = gosettings.DefaultPointer(w.PersistentKeepaliveInterval, 0) w.Interface = gosettings.DefaultString(w.Interface, "wg0")
w.Interface = gosettings.DefaultComparable(w.Interface, "wg0")
const defaultMTU = 1400 const defaultMTU = 1400
w.MTU = gosettings.DefaultComparable(w.MTU, defaultMTU) w.MTU = gosettings.DefaultNumber(w.MTU, defaultMTU)
w.Implementation = gosettings.DefaultComparable(w.Implementation, "auto") w.Implementation = gosettings.DefaultString(w.Implementation, "auto")
} }
func (w Wireguard) String() string { func (w Wireguard) String() string {
@@ -215,16 +205,12 @@ func (w Wireguard) toLinesNode() (node *gotree.Node) {
addressesNode := node.Appendf("Interface addresses:") addressesNode := node.Appendf("Interface addresses:")
for _, address := range w.Addresses { for _, address := range w.Addresses {
addressesNode.Append(address.String()) addressesNode.Appendf(address.String())
} }
allowedIPsNode := node.Appendf("Allowed IPs:") allowedIPsNode := node.Appendf("Allowed IPs:")
for _, allowedIP := range w.AllowedIPs { for _, allowedIP := range w.AllowedIPs {
allowedIPsNode.Append(allowedIP.String()) allowedIPsNode.Appendf(allowedIP.String())
}
if *w.PersistentKeepaliveInterval > 0 {
node.Appendf("Persistent keepalive interval: %s", w.PersistentKeepaliveInterval)
} }
interfaceNode := node.Appendf("Network interface: %s", w.Interface) interfaceNode := node.Appendf("Network interface: %s", w.Interface)
@@ -236,44 +222,3 @@ func (w Wireguard) toLinesNode() (node *gotree.Node) {
return node return node
} }
func (w *Wireguard) read(r *reader.Reader) (err error) {
w.PrivateKey = r.Get("WIREGUARD_PRIVATE_KEY", reader.ForceLowercase(false))
w.PreSharedKey = r.Get("WIREGUARD_PRESHARED_KEY", reader.ForceLowercase(false))
w.Interface = r.String("VPN_INTERFACE",
reader.RetroKeys("WIREGUARD_INTERFACE"), reader.ForceLowercase(false))
w.Implementation = r.String("WIREGUARD_IMPLEMENTATION")
addressStrings := r.CSV("WIREGUARD_ADDRESSES", reader.RetroKeys("WIREGUARD_ADDRESS"))
// WARNING: do not initialize w.Addresses to an empty slice
// or the defaults for nordvpn will not work.
for _, addressString := range addressStrings {
if !strings.ContainsRune(addressString, '/') {
addressString += "/32"
}
addressString = strings.TrimSpace(addressString)
address, err := netip.ParsePrefix(addressString)
if err != nil {
return fmt.Errorf("parsing address: %w", err)
}
w.Addresses = append(w.Addresses, address)
}
w.AllowedIPs, err = r.CSVNetipPrefixes("WIREGUARD_ALLOWED_IPS")
if err != nil {
return err // already wrapped
}
w.PersistentKeepaliveInterval, err = r.DurationPtr("WIREGUARD_PERSISTENT_KEEPALIVE_INTERVAL")
if err != nil {
return err
}
mtuPtr, err := r.Uint16Ptr("WIREGUARD_MTU")
if err != nil {
return err
} else if mtuPtr != nil {
w.MTU = *mtuPtr
}
return nil
}

View File

@@ -6,7 +6,6 @@ import (
"github.com/qdm12/gluetun/internal/constants/providers" "github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gosettings" "github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/reader"
"github.com/qdm12/gosettings/validate" "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"
@@ -38,9 +37,8 @@ type WireguardSelection struct {
func (w WireguardSelection) validate(vpnProvider string) (err error) { func (w WireguardSelection) validate(vpnProvider string) (err error) {
// Validate EndpointIP // Validate EndpointIP
switch vpnProvider { switch vpnProvider {
case providers.Airvpn, providers.Fastestvpn, providers.Ivpn, case providers.Airvpn, providers.Ivpn, providers.Mullvad,
providers.Mullvad, providers.Nordvpn, providers.Protonvpn, providers.Nordvpn, providers.Surfshark, providers.Windscribe:
providers.Surfshark, providers.Windscribe:
// endpoint IP addresses are baked in // endpoint IP addresses are baked in
case providers.Custom: case providers.Custom:
if !w.EndpointIP.IsValid() || w.EndpointIP.IsUnspecified() { if !w.EndpointIP.IsValid() || w.EndpointIP.IsUnspecified() {
@@ -57,8 +55,7 @@ func (w WireguardSelection) validate(vpnProvider string) (err error) {
return fmt.Errorf("%w", ErrWireguardEndpointPortNotSet) return fmt.Errorf("%w", ErrWireguardEndpointPortNotSet)
} }
// EndpointPort cannot be set // EndpointPort cannot be set
case providers.Fastestvpn, providers.Nordvpn, case providers.Surfshark, providers.Nordvpn:
providers.Protonvpn, providers.Surfshark:
if *w.EndpointPort != 0 { if *w.EndpointPort != 0 {
return fmt.Errorf("%w", ErrWireguardEndpointPortSet) return fmt.Errorf("%w", ErrWireguardEndpointPortSet)
} }
@@ -91,7 +88,7 @@ func (w WireguardSelection) validate(vpnProvider string) (err error) {
// Validate PublicKey // Validate PublicKey
switch vpnProvider { switch vpnProvider {
case providers.Fastestvpn, providers.Ivpn, providers.Mullvad, case providers.Ivpn, providers.Mullvad,
providers.Surfshark, providers.Windscribe: providers.Surfshark, providers.Windscribe:
// public keys are baked in // public keys are baked in
case providers.Custom: case providers.Custom:
@@ -119,10 +116,16 @@ func (w *WireguardSelection) copy() (copied WireguardSelection) {
} }
} }
func (w *WireguardSelection) mergeWith(other WireguardSelection) {
w.EndpointIP = gosettings.MergeWithValidator(w.EndpointIP, other.EndpointIP)
w.EndpointPort = gosettings.MergeWithPointer(w.EndpointPort, other.EndpointPort)
w.PublicKey = gosettings.MergeWithString(w.PublicKey, other.PublicKey)
}
func (w *WireguardSelection) overrideWith(other WireguardSelection) { func (w *WireguardSelection) overrideWith(other WireguardSelection) {
w.EndpointIP = gosettings.OverrideWithValidator(w.EndpointIP, other.EndpointIP) w.EndpointIP = gosettings.OverrideWithValidator(w.EndpointIP, other.EndpointIP)
w.EndpointPort = gosettings.OverrideWithPointer(w.EndpointPort, other.EndpointPort) w.EndpointPort = gosettings.OverrideWithPointer(w.EndpointPort, other.EndpointPort)
w.PublicKey = gosettings.OverrideWithComparable(w.PublicKey, other.PublicKey) w.PublicKey = gosettings.OverrideWithString(w.PublicKey, other.PublicKey)
} }
func (w *WireguardSelection) setDefaults() { func (w *WireguardSelection) setDefaults() {
@@ -151,18 +154,3 @@ func (w WireguardSelection) toLinesNode() (node *gotree.Node) {
return node return node
} }
func (w *WireguardSelection) read(r *reader.Reader) (err error) {
w.EndpointIP, err = r.NetipAddr("WIREGUARD_ENDPOINT_IP", reader.RetroKeys("VPN_ENDPOINT_IP"))
if err != nil {
return err
}
w.EndpointPort, err = r.Uint16Ptr("WIREGUARD_ENDPOINT_PORT", reader.RetroKeys("VPN_ENDPOINT_PORT"))
if err != nil {
return err
}
w.PublicKey = r.String("WIREGUARD_PUBLIC_KEY", reader.ForceLowercase(false))
return nil
}

View File

@@ -0,0 +1,55 @@
package env
import (
"fmt"
"net/netip"
"github.com/qdm12/gluetun/internal/configuration/settings"
)
func (s *Source) readDNS() (dns settings.DNS, err error) {
dns.ServerAddress, err = s.readDNSServerAddress()
if err != nil {
return dns, err
}
dns.KeepNameserver, err = s.env.BoolPtr("DNS_KEEP_NAMESERVER")
if err != nil {
return dns, err
}
dns.DoT, err = s.readDoT()
if err != nil {
return dns, fmt.Errorf("DoT settings: %w", err)
}
return dns, nil
}
func (s *Source) readDNSServerAddress() (address netip.Addr, err error) {
const currentKey = "DNS_ADDRESS"
key := firstKeySet(s.env, "DNS_PLAINTEXT_ADDRESS", currentKey)
switch key {
case "":
return address, nil
case currentKey:
default: // Retro-compatibility
s.handleDeprecatedKey(key, currentKey)
}
address, err = s.env.NetipAddr(key)
if err != nil {
return address, err
}
// TODO remove in v4
if address.Unmap().Compare(netip.AddrFrom4([4]byte{127, 0, 0, 1})) != 0 {
s.warner.Warn(key + " is set to " + address.String() +
" so the DNS over TLS (DoT) server will not be used." +
" The default value changed to 127.0.0.1 so it uses the internal DoT serves." +
" If the DoT server fails to start, the IPv4 address of the first plaintext DNS server" +
" corresponding to the first DoT provider chosen is used.")
}
return address, nil
}

View File

@@ -0,0 +1,73 @@
package env
import (
"errors"
"fmt"
"net/netip"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gosettings/sources/env"
)
func (s *Source) readDNSBlacklist() (blacklist settings.DNSBlacklist, err error) {
blacklist.BlockMalicious, err = s.env.BoolPtr("BLOCK_MALICIOUS")
if err != nil {
return blacklist, err
}
blacklist.BlockSurveillance, err = s.env.BoolPtr("BLOCK_SURVEILLANCE",
env.RetroKeys("BLOCK_NSA"))
if err != nil {
return blacklist, err
}
blacklist.BlockAds, err = s.env.BoolPtr("BLOCK_ADS")
if err != nil {
return blacklist, err
}
blacklist.AddBlockedIPs, blacklist.AddBlockedIPPrefixes,
err = s.readDoTPrivateAddresses() // TODO v4 split in 2
if err != nil {
return blacklist, err
}
blacklist.AllowedHosts = s.env.CSV("UNBLOCK") // TODO v4 change name
return blacklist, nil
}
var (
ErrPrivateAddressNotValid = errors.New("private address is not a valid IP or CIDR range")
)
func (s *Source) readDoTPrivateAddresses() (ips []netip.Addr,
ipPrefixes []netip.Prefix, err error) {
privateAddresses := s.env.CSV("DOT_PRIVATE_ADDRESS")
if len(privateAddresses) == 0 {
return nil, nil, nil
}
ips = make([]netip.Addr, 0, len(privateAddresses))
ipPrefixes = make([]netip.Prefix, 0, len(privateAddresses))
for _, privateAddress := range privateAddresses {
ip, err := netip.ParseAddr(privateAddress)
if err == nil {
ips = append(ips, ip)
continue
}
ipPrefix, err := netip.ParsePrefix(privateAddress)
if err == nil {
ipPrefixes = append(ipPrefixes, ipPrefix)
continue
}
return nil, nil, fmt.Errorf(
"environment variable DOT_PRIVATE_ADDRESS: %w: %s",
ErrPrivateAddressNotValid, privateAddress)
}
return ips, ipPrefixes, nil
}

View File

@@ -0,0 +1,29 @@
package env
import (
"github.com/qdm12/gluetun/internal/configuration/settings"
)
func (s *Source) readDoT() (dot settings.DoT, err error) {
dot.Enabled, err = s.env.BoolPtr("DOT")
if err != nil {
return dot, err
}
dot.UpdatePeriod, err = s.env.DurationPtr("DNS_UPDATE_PERIOD")
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 {
return dot, err
}
return dot, nil
}

View File

@@ -0,0 +1,36 @@
package env
import (
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gosettings/sources/env"
)
func (s *Source) readFirewall() (firewall settings.Firewall, err error) {
firewall.VPNInputPorts, err = s.env.CSVUint16("FIREWALL_VPN_INPUT_PORTS")
if err != nil {
return firewall, err
}
firewall.InputPorts, err = s.env.CSVUint16("FIREWALL_INPUT_PORTS")
if err != nil {
return firewall, err
}
firewall.OutboundSubnets, err = s.env.CSVNetipPrefixes("FIREWALL_OUTBOUND_SUBNETS",
env.RetroKeys("EXTRA_SUBNETS"))
if err != nil {
return firewall, err
}
firewall.Enabled, err = s.env.BoolPtr("FIREWALL")
if err != nil {
return firewall, err
}
firewall.Debug, err = s.env.BoolPtr("FIREWALL_DEBUG")
if err != nil {
return firewall, err
}
return firewall, nil
}

View File

@@ -0,0 +1,35 @@
package env
import (
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gosettings/sources/env"
)
func (s *Source) ReadHealth() (health settings.Health, err error) {
health.ServerAddress = s.env.String("HEALTH_SERVER_ADDRESS")
health.TargetAddress = s.env.String("HEALTH_TARGET_ADDRESS",
env.RetroKeys("HEALTH_ADDRESS_TO_PING"))
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_DURATION_INITIAL",
env.RetroKeys("HEALTH_OPENVPN_DURATION_INITIAL"))
if err != nil {
return health, err
}
health.VPN.Addition, err = s.env.DurationPtr(
"HEALTH_VPN_DURATION_ADDITION",
env.RetroKeys("HEALTH_OPENVPN_DURATION_ADDITION"))
if err != nil {
return health, err
}
return health, nil
}

View File

@@ -0,0 +1,33 @@
package env
import (
"fmt"
"os"
"github.com/qdm12/gosettings/sources/env"
)
func unsetEnvKeys(envKeys []string, err error) (newErr error) {
newErr = err
for _, envKey := range envKeys {
unsetErr := os.Unsetenv(envKey)
if unsetErr != nil && newErr == nil {
newErr = fmt.Errorf("unsetting environment variable %s: %w", envKey, unsetErr)
}
}
return newErr
}
func ptrTo[T any](value T) *T {
return &value
}
func firstKeySet(e env.Env, keys ...string) (firstKeySet string) {
for _, key := range keys {
value := e.Get(key)
if value != nil {
return key
}
}
return ""
}

View File

@@ -0,0 +1,84 @@
package env
import (
"fmt"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gosettings/sources/env"
"github.com/qdm12/govalid/binary"
)
func (s *Source) readHTTPProxy() (httpProxy settings.HTTPProxy, err error) {
httpProxy.User = s.env.Get("HTTPPROXY_USER",
env.RetroKeys("PROXY_USER", "TINYPROXY_USER"),
env.ForceLowercase(false))
httpProxy.Password = s.env.Get("HTTPPROXY_PASSWORD",
env.RetroKeys("PROXY_PASSWORD", "TINYPROXY_PASSWORD"),
env.ForceLowercase(false))
httpProxy.ListeningAddress, err = s.readHTTProxyListeningAddress()
if err != nil {
return httpProxy, err
}
httpProxy.Enabled, err = s.env.BoolPtr("HTTPPROXY", env.RetroKeys("PROXY", "TINYPROXY"))
if err != nil {
return httpProxy, err
}
httpProxy.Stealth, err = s.env.BoolPtr("HTTPPROXY_STEALTH")
if err != nil {
return httpProxy, err
}
httpProxy.Log, err = s.readHTTProxyLog()
if err != nil {
return httpProxy, err
}
return httpProxy, nil
}
func (s *Source) readHTTProxyListeningAddress() (listeningAddress string, err error) {
const currentKey = "HTTPPROXY_LISTENING_ADDRESS"
key := firstKeySet(s.env, "HTTPPROXY_PORT", "TINYPROXY_PORT", "PROXY_PORT",
currentKey)
switch key {
case "":
return "", nil
case currentKey:
return s.env.String(key), nil
}
// Retro-compatible keys using a port only
s.handleDeprecatedKey(key, currentKey)
port, err := s.env.Uint16Ptr(key)
if err != nil {
return "", err
}
return fmt.Sprintf(":%d", *port), nil
}
func (s *Source) readHTTProxyLog() (enabled *bool, err error) {
const currentKey = "HTTPPROXY_LOG"
key := firstKeySet(s.env, "PROXY_LOG", "TINYPROXY_LOG", "HTTPPROXY_LOG")
switch key {
case "":
return nil, nil //nolint:nilnil
case currentKey:
return s.env.BoolPtr(key)
}
// Retro-compatible keys using different boolean verbs
s.handleDeprecatedKey(key, currentKey)
value := s.env.String(key)
retroOption := binary.OptionEnabled("on", "info", "connect", "notice")
enabled, err = binary.Validate(value, retroOption)
if err != nil {
return nil, fmt.Errorf("environment variable %s: %w", key, err)
}
return enabled, nil
}

View File

@@ -0,0 +1,53 @@
package env
import (
"errors"
"fmt"
"strings"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/log"
)
func (s *Source) readLog() (log settings.Log, err error) {
log.Level, err = s.readLogLevel()
if err != nil {
return log, err
}
return log, nil
}
func (s *Source) readLogLevel() (level *log.Level, err error) {
value := s.env.String("LOG_LEVEL")
if value == "" {
return nil, nil //nolint:nilnil
}
level = new(log.Level)
*level, err = parseLogLevel(value)
if err != nil {
return nil, fmt.Errorf("environment variable LOG_LEVEL: %w", err)
}
return level, nil
}
var ErrLogLevelUnknown = errors.New("log level is unknown")
func parseLogLevel(s string) (level log.Level, err error) {
switch strings.ToLower(s) {
case "debug":
return log.LevelDebug, nil
case "info":
return log.LevelInfo, nil
case "warning":
return log.LevelWarn, nil
case "error":
return log.LevelError, nil
default:
return level, fmt.Errorf(
"%w: %q is not valid and can be one of debug, info, warning or error",
ErrLogLevelUnknown, s)
}
}

View File

@@ -0,0 +1,77 @@
package env
import (
"strings"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gosettings/sources/env"
)
func (s *Source) readOpenVPN() (
openVPN settings.OpenVPN, err error) {
defer func() {
err = unsetEnvKeys([]string{"OPENVPN_KEY", "OPENVPN_CERT",
"OPENVPN_KEY_PASSPHRASE", "OPENVPN_ENCRYPTED_KEY"}, err)
}()
openVPN.Version = s.env.String("OPENVPN_VERSION")
openVPN.User = s.env.Get("OPENVPN_USER",
env.RetroKeys("USER"), env.ForceLowercase(false))
openVPN.Password = s.env.Get("OPENVPN_PASSWORD",
env.RetroKeys("PASSWORD"), env.ForceLowercase(false))
openVPN.ConfFile = s.env.Get("OPENVPN_CUSTOM_CONFIG", env.ForceLowercase(false))
openVPN.Ciphers = s.env.CSV("OPENVPN_CIPHERS", env.RetroKeys("OPENVPN_CIPHER"))
openVPN.Auth = s.env.Get("OPENVPN_AUTH")
openVPN.Cert = s.env.Get("OPENVPN_CERT", env.ForceLowercase(false))
openVPN.Key = s.env.Get("OPENVPN_KEY", env.ForceLowercase(false))
openVPN.EncryptedKey = s.env.Get("OPENVPN_ENCRYPTED_KEY", env.ForceLowercase(false))
openVPN.KeyPassphrase = s.env.Get("OPENVPN_KEY_PASSPHRASE", env.ForceLowercase(false))
openVPN.PIAEncPreset = s.readPIAEncryptionPreset()
openVPN.MSSFix, err = s.env.Uint16Ptr("OPENVPN_MSSFIX")
if err != nil {
return openVPN, err
}
openVPN.Interface = s.env.String("VPN_INTERFACE",
env.RetroKeys("OPENVPN_INTERFACE"), env.ForceLowercase(false))
openVPN.ProcessUser, err = s.readOpenVPNProcessUser()
if err != nil {
return openVPN, err
}
openVPN.Verbosity, err = s.env.IntPtr("OPENVPN_VERBOSITY")
if err != nil {
return openVPN, err
}
flagsPtr := s.env.Get("OPENVPN_FLAGS", env.ForceLowercase(false))
if flagsPtr != nil {
openVPN.Flags = strings.Fields(*flagsPtr)
}
return openVPN, nil
}
func (s *Source) readPIAEncryptionPreset() (presetPtr *string) {
return s.env.Get(
"PRIVATE_INTERNET_ACCESS_OPENVPN_ENCRYPTION_PRESET",
env.RetroKeys("ENCRYPTION", "PIA_ENCRYPTION"))
}
func (s *Source) readOpenVPNProcessUser() (processUser string, err error) {
value, err := s.env.BoolPtr("OPENVPN_ROOT") // Retro-compatibility
if err != nil {
return "", err
} else if value != nil {
if *value {
return "root", nil
}
const defaultNonRootUser = "nonrootuser"
return defaultNonRootUser, nil
}
return s.env.String("OPENVPN_PROCESS_USER"), nil
}

View File

@@ -0,0 +1,56 @@
package env
import (
"errors"
"fmt"
"strings"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gosettings/sources/env"
)
func (s *Source) readOpenVPNSelection() (
selection settings.OpenVPNSelection, err error) {
selection.ConfFile = s.env.Get("OPENVPN_CUSTOM_CONFIG", env.ForceLowercase(false))
selection.TCP, err = s.readOpenVPNProtocol()
if err != nil {
return selection, err
}
selection.CustomPort, err = s.env.Uint16Ptr("VPN_ENDPOINT_PORT",
env.RetroKeys("PORT", "OPENVPN_PORT"))
if err != nil {
return selection, err
}
selection.PIAEncPreset = s.readPIAEncryptionPreset()
return selection, nil
}
var ErrOpenVPNProtocolNotValid = errors.New("OpenVPN protocol is not valid")
func (s *Source) readOpenVPNProtocol() (tcp *bool, err error) {
const currentKey = "OPENVPN_PROTOCOL"
envKey := firstKeySet(s.env, "PROTOCOL", currentKey)
switch envKey {
case "":
return nil, nil //nolint:nilnil
case currentKey:
default: // Retro compatibility
s.handleDeprecatedKey(envKey, currentKey)
}
protocol := s.env.String(envKey)
switch strings.ToLower(protocol) {
case constants.UDP:
return ptrTo(false), nil
case constants.TCP:
return ptrTo(true), nil
default:
return nil, fmt.Errorf("environment variable %s: %w: %s",
envKey, ErrOpenVPNProtocolNotValid, protocol)
}
}

View File

@@ -0,0 +1,34 @@
package env
import (
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gosettings/sources/env"
)
func (s *Source) readPortForward() (
portForwarding settings.PortForwarding, err error) {
portForwarding.Enabled, err = s.env.BoolPtr("VPN_PORT_FORWARDING",
env.RetroKeys(
"PORT_FORWARDING",
"PRIVATE_INTERNET_ACCESS_VPN_PORT_FORWARDING",
))
if err != nil {
return portForwarding, err
}
portForwarding.Provider = s.env.Get("VPN_PORT_FORWARDING_PROVIDER")
portForwarding.Filepath = s.env.Get("VPN_PORT_FORWARDING_STATUS_FILE",
env.ForceLowercase(false),
env.RetroKeys(
"PORT_FORWARDING_STATUS_FILE",
"PRIVATE_INTERNET_ACCESS_VPN_PORT_FORWARDING_STATUS_FILE",
))
portForwarding.ListeningPort, err = s.env.Uint16Ptr("VPN_PORT_FORWARDING_LISTENING_PORT")
if err != nil {
return portForwarding, err
}
return portForwarding, nil
}

View File

@@ -0,0 +1,26 @@
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
}

View File

@@ -0,0 +1,50 @@
package env
import (
"fmt"
"strings"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/constants/vpn"
"github.com/qdm12/gosettings/sources/env"
)
func (s *Source) readProvider(vpnType string) (provider settings.Provider, err error) {
provider.Name = s.readVPNServiceProvider(vpnType)
var providerName string
if provider.Name != nil {
providerName = *provider.Name
}
provider.ServerSelection, err = s.readServerSelection(providerName, vpnType)
if err != nil {
return provider, fmt.Errorf("server selection: %w", err)
}
provider.PortForwarding, err = s.readPortForward()
if err != nil {
return provider, fmt.Errorf("port forwarding: %w", err)
}
return provider, nil
}
func (s *Source) readVPNServiceProvider(vpnType string) (vpnProviderPtr *string) {
valuePtr := s.env.Get("VPN_SERVICE_PROVIDER", env.RetroKeys("VPNSP"))
if valuePtr == nil {
if vpnType != vpn.Wireguard && s.env.Get("OPENVPN_CUSTOM_CONFIG") != nil {
// retro compatibility
return ptrTo(providers.Custom)
}
return nil
}
value := *valuePtr
value = strings.ToLower(value)
if value == "pia" { // retro compatibility
return ptrTo(providers.PrivateInternetAccess)
}
return ptrTo(value)
}

View File

@@ -0,0 +1,22 @@
package env
import (
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gosettings/sources/env"
)
func (s *Source) readPublicIP() (publicIP settings.PublicIP, err error) {
publicIP.Period, err = s.env.DurationPtr("PUBLICIP_PERIOD")
if err != nil {
return publicIP, err
}
publicIP.IPFilepath = s.env.Get("PUBLICIP_FILE",
env.ForceLowercase(false), env.RetroKeys("IP_STATUS_FILE"))
publicIP.API = s.env.String("PUBLICIP_API")
publicIP.APIToken = s.env.Get("PUBLICIP_API_TOKEN")
return publicIP, nil
}

View File

@@ -0,0 +1,103 @@
package env
import (
"os"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gosettings/sources/env"
)
type Source struct {
env env.Env
warner Warner
handleDeprecatedKey func(deprecatedKey, newKey string)
}
type Warner interface {
Warn(s string)
}
func New(warner Warner) *Source {
handleDeprecatedKey := func(deprecatedKey, newKey string) {
warner.Warn(
"You are using the old environment variable " + deprecatedKey +
", please consider changing it to " + newKey)
}
return &Source{
env: *env.New(os.Environ(), handleDeprecatedKey),
warner: warner,
handleDeprecatedKey: handleDeprecatedKey,
}
}
func (s *Source) String() string { return "environment variables" }
func (s *Source) Read() (settings settings.Settings, err error) {
settings.VPN, err = s.readVPN()
if err != nil {
return settings, err
}
settings.Firewall, err = s.readFirewall()
if err != nil {
return settings, err
}
settings.System, err = s.readSystem()
if err != nil {
return settings, err
}
settings.Health, err = s.ReadHealth()
if err != nil {
return settings, err
}
settings.HTTPProxy, err = s.readHTTPProxy()
if err != nil {
return settings, err
}
settings.Log, err = s.readLog()
if err != nil {
return settings, err
}
settings.PublicIP, err = s.readPublicIP()
if err != nil {
return settings, err
}
settings.Updater, err = s.readUpdater()
if err != nil {
return settings, err
}
settings.Version, err = s.readVersion()
if err != nil {
return settings, err
}
settings.Shadowsocks, err = s.readShadowsocks()
if err != nil {
return settings, err
}
settings.DNS, err = s.readDNS()
if err != nil {
return settings, err
}
settings.ControlServer, err = s.readControlServer()
if err != nil {
return settings, err
}
settings.Pprof, err = s.readPprof()
if err != nil {
return settings, err
}
return settings, nil
}

View File

@@ -0,0 +1,31 @@
package env
import (
"github.com/qdm12/gluetun/internal/configuration/settings"
)
func (s *Source) readControlServer() (controlServer settings.ControlServer, err error) {
controlServer.Log, err = s.env.BoolPtr("HTTP_CONTROL_SERVER_LOG")
if err != nil {
return controlServer, err
}
controlServer.Address = s.readControlServerAddress()
return controlServer, nil
}
func (s *Source) readControlServerAddress() (address *string) {
const currentKey = "HTTP_CONTROL_SERVER_ADDRESS"
key := firstKeySet(s.env, "CONTROL_SERVER_ADDRESS", currentKey)
if key == currentKey {
return s.env.Get(key)
}
s.handleDeprecatedKey(key, currentKey)
value := s.env.Get("CONTROL_SERVER_ADDRESS")
if value == nil {
return nil
}
return ptrTo(":" + *value)
}

View File

@@ -0,0 +1,92 @@
package env
import (
"errors"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gosettings/sources/env"
)
func (s *Source) readServerSelection(vpnProvider, vpnType string) (
ss settings.ServerSelection, err error) {
ss.VPN = vpnType
ss.TargetIP, err = s.env.NetipAddr("VPN_ENDPOINT_IP",
env.RetroKeys("OPENVPN_TARGET_IP"))
if err != nil {
return ss, err
}
ss.Countries = s.env.CSV("SERVER_COUNTRIES", env.RetroKeys("COUNTRY"))
if vpnProvider == providers.Cyberghost && len(ss.Countries) == 0 {
// Retro-compatibility for Cyberghost using the REGION variable
ss.Countries = s.env.CSV("REGION")
if len(ss.Countries) > 0 {
s.handleDeprecatedKey("REGION", "SERVER_COUNTRIES")
}
}
ss.Regions = s.env.CSV("SERVER_REGIONS", env.RetroKeys("REGION"))
ss.Cities = s.env.CSV("SERVER_CITIES", env.RetroKeys("CITY"))
ss.ISPs = s.env.CSV("ISP")
ss.Hostnames = s.env.CSV("SERVER_HOSTNAMES", env.RetroKeys("SERVER_HOSTNAME"))
ss.Names = s.env.CSV("SERVER_NAMES", env.RetroKeys("SERVER_NAME"))
ss.Numbers, err = s.env.CSVUint16("SERVER_NUMBER")
ss.Categories = s.env.CSV("SERVER_CATEGORIES")
if err != nil {
return ss, err
}
// Mullvad only
ss.OwnedOnly, err = s.env.BoolPtr("OWNED_ONLY", env.RetroKeys("OWNED"))
if err != nil {
return ss, err
}
// VPNUnlimited and ProtonVPN only
ss.FreeOnly, err = s.env.BoolPtr("FREE_ONLY")
if err != nil {
return ss, err
}
// VPNSecure only
ss.PremiumOnly, err = s.env.BoolPtr("PREMIUM_ONLY")
if err != nil {
return ss, err
}
// Surfshark only
ss.MultiHopOnly, err = s.env.BoolPtr("MULTIHOP_ONLY")
if err != nil {
return ss, err
}
// VPNUnlimited only
ss.StreamOnly, err = s.env.BoolPtr("STREAM_ONLY")
if err != nil {
return ss, err
}
// PIA only
ss.PortForwardOnly, err = s.env.BoolPtr("PORT_FORWARD_ONLY")
if err != nil {
return ss, err
}
ss.OpenVPN, err = s.readOpenVPNSelection()
if err != nil {
return ss, err
}
ss.Wireguard, err = s.readWireguardSelection()
if err != nil {
return ss, err
}
return ss, nil
}
var (
ErrInvalidIP = errors.New("invalid IP address")
)

View File

@@ -0,0 +1,42 @@
package env
import (
"fmt"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gosettings/sources/env"
)
func (s *Source) readShadowsocks() (shadowsocks settings.Shadowsocks, err error) {
shadowsocks.Enabled, err = s.env.BoolPtr("SHADOWSOCKS")
if err != nil {
return shadowsocks, err
}
shadowsocks.Address, err = s.readShadowsocksAddress()
if err != nil {
return shadowsocks, err
}
shadowsocks.LogAddresses, err = s.env.BoolPtr("SHADOWSOCKS_LOG")
if err != nil {
return shadowsocks, err
}
shadowsocks.CipherName = s.env.String("SHADOWSOCKS_CIPHER",
env.RetroKeys("SHADOWSOCKS_METHOD"))
shadowsocks.Password = s.env.Get("SHADOWSOCKS_PASSWORD", env.ForceLowercase(false))
return shadowsocks, nil
}
func (s *Source) readShadowsocksAddress() (address *string, err error) {
const currentKey = "SHADOWSOCKS_LISTENING_ADDRESS"
port, err := s.env.Uint16Ptr("SHADOWSOCKS_PORT") // retro-compatibility
if err != nil {
return nil, err
} else if port != nil {
s.handleDeprecatedKey("SHADOWSOCKS_PORT", currentKey)
return ptrTo(fmt.Sprintf(":%d", *port)), nil
}
return s.env.Get(currentKey), nil
}

View File

@@ -0,0 +1,22 @@
package env
import (
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gosettings/sources/env"
)
func (s *Source) readSystem() (system settings.System, err error) {
system.PUID, err = s.env.Uint32Ptr("PUID", env.RetroKeys("UID"))
if err != nil {
return system, err
}
system.PGID, err = s.env.Uint32Ptr("PGID", env.RetroKeys("GID"))
if err != nil {
return system, err
}
system.Timezone = s.env.String("TZ")
return system, nil
}

View File

@@ -0,0 +1,36 @@
package env
import (
"github.com/qdm12/gluetun/internal/configuration/settings"
)
func (s *Source) readUnbound() (unbound settings.Unbound, err error) {
unbound.Providers = s.env.CSV("DOT_PROVIDERS")
unbound.Caching, err = s.env.BoolPtr("DOT_CACHING")
if err != nil {
return unbound, err
}
unbound.IPv6, err = s.env.BoolPtr("DOT_IPV6")
if err != nil {
return unbound, err
}
unbound.VerbosityLevel, err = s.env.Uint8Ptr("DOT_VERBOSITY")
if err != nil {
return unbound, err
}
unbound.VerbosityDetailsLevel, err = s.env.Uint8Ptr("DOT_VERBOSITY_DETAILS")
if err != nil {
return unbound, err
}
unbound.ValidationLogLevel, err = s.env.Uint8Ptr("DOT_VALIDATION_LOGLEVEL")
if err != nil {
return unbound, err
}
return unbound, nil
}

View File

@@ -0,0 +1,35 @@
package env
import (
"github.com/qdm12/gluetun/internal/configuration/settings"
)
func (s *Source) readUpdater() (updater settings.Updater, err error) {
updater.Period, err = s.env.DurationPtr("UPDATER_PERIOD")
if err != nil {
return updater, err
}
updater.DNSAddress, err = readUpdaterDNSAddress()
if err != nil {
return updater, err
}
updater.MinRatio, err = s.env.Float64("UPDATER_MIN_RATIO")
if err != nil {
return updater, err
}
updater.Providers = s.env.CSV("UPDATER_VPN_SERVICE_PROVIDERS")
return updater, nil
}
func readUpdaterDNSAddress() (address string, err error) {
// TODO this is currently using Cloudflare in
// plaintext to not be blocked by DNS over TLS by default.
// If a plaintext address is set in the DNS settings, this one will be used.
// use custom future encrypted DNS written in Go without blocking
// as it's too much trouble to start another parallel unbound instance for now.
return "", nil
}

View File

@@ -0,0 +1,14 @@
package env
import (
"github.com/qdm12/gluetun/internal/configuration/settings"
)
func (s *Source) readVersion() (version settings.Version, err error) {
version.Enabled, err = s.env.BoolPtr("VERSION_INFORMATION")
if err != nil {
return version, err
}
return version, nil
}

View File

@@ -0,0 +1,28 @@
package env
import (
"fmt"
"github.com/qdm12/gluetun/internal/configuration/settings"
)
func (s *Source) readVPN() (vpn settings.VPN, err error) {
vpn.Type = s.env.String("VPN_TYPE")
vpn.Provider, err = s.readProvider(vpn.Type)
if err != nil {
return vpn, fmt.Errorf("VPN provider: %w", err)
}
vpn.OpenVPN, err = s.readOpenVPN()
if err != nil {
return vpn, fmt.Errorf("OpenVPN: %w", err)
}
vpn.Wireguard, err = s.readWireguard()
if err != nil {
return vpn, fmt.Errorf("wireguard: %w", err)
}
return vpn, nil
}

Some files were not shown because too many files have changed in this diff Show More