Skip to content

Commit 1c97a4c

Browse files
committed
⚓️ Add test_vpn.sh and make the fleet sturdier: healthcheck for Gluetun, dependencies for VPN-bound crew
1 parent de5d344 commit 1c97a4c

File tree

7 files changed

+205
-31
lines changed

7 files changed

+205
-31
lines changed

.vscode/settings.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,12 @@
3535
"goin",
3636
"guidin",
3737
"handlin",
38+
"healthcheck",
39+
"healthchecks",
3840
"helpin",
3941
"holdin",
4042
"IPAM",
43+
"IPINFO",
4144
"keepin",
4245
"linebreak",
4346
"linkify",

Makefile

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,9 @@ COMPOSE_UP_OPTIONS ?= --force-recreate --pull always --detach
4646
COMPOSE_LOGS_OPTIONS ?= --follow
4747

4848
#
49-
# Testing options
49+
# Testing commands
5050
#
51-
DOCKER_VPN_CONTAINER ?= $(COMPOSE_GLUETUN_SERVICE)-latest
52-
DOCKER_VPN_TEST_IMAGE ?= alpine:3.18
53-
DOCKER_VPN_TEST_CMD ?= sh -c "apk add wget && wget -qO- https://ipinfo.io"
51+
DOCKER_VPN_TEST_CMD ?= sh scripts/test_vpn.sh
5452

5553
#
5654
# Build dependencies
@@ -130,7 +128,7 @@ $(START): $(BUILD_DEPENDS)
130128
# $(TEST_VPN): Obtains the VPN IP address and ensure the connection is working.
131129
#
132130
$(TEST_VPN):
133-
docker run --rm --network=container:$(DOCKER_VPN_CONTAINER) $(DOCKER_VPN_TEST_IMAGE) $(DOCKER_VPN_TEST_CMD)
131+
$(DOCKER_VPN_TEST_CMD)
134132

135133
#
136134
# $(CONFIG): Renders the actual data model to be applied on the Docker Engine.

README.md

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -119,18 +119,38 @@ To confirm yer VPN sails be catchin' wind:
119119

120120
```bash
121121
❯ make test-vpn
122-
docker run --rm --network=container:gluetun-latest alpine:3.18 sh -c "apk add wget && wget -qO- https://ipinfo.io"
122+
sh scripts/test_vpn.sh
123+
Running VPN container test...
124+
Step 1: Running Docker container with VPN network:
125+
docker run --rm --network=container:gluetun-latest alpine:latest sh -c 'apk add --no-cache wget >/dev/null 2>&1 && wget -qO- https://ipinfo.io'
126+
Step 2: Received container response:
123127
{
124-
"ip": "172.16.0.42",
128+
"ip": "172.16.88.88",
125129
"city": "Tortuga",
126-
"region": "Caribbean",
127-
"country": "High Seas",
128-
"loc": "13.4443,-144.7937",
129-
"org": "Black Pearl Privateers",
130-
"postal": "ARR123",
131-
"timezone": "Rum/Somewhere",
132-
"readme": "https://ipinfo.io/blackpearl"
130+
"region": "Rum Islands",
131+
"country": "XP",
132+
"loc": "21.4200,-71.1419",
133+
"org": "AS7777 The Jolly Rogers",
134+
"postal": "00000",
135+
"timezone": "Ocean/HighSeas",
136+
"readme": "https://ipinfo.io/missingauth"
133137
}
138+
Step 3: Getting host IP info from ipinfo.io...
139+
Step 4: Received host response:
140+
{
141+
"ip": "10.42.42.42",
142+
"hostname": "flagship.plundarr.local",
143+
"city": "Port Royal",
144+
"region": "Skull Coast",
145+
"country": "XP",
146+
"loc": "17.9355,-76.8419",
147+
"org": "AS1492 Blackbeard’s Backbone Ltd.",
148+
"postal": "99999",
149+
"timezone": "Ocean/SkullBay",
150+
"readme": "https://ipinfo.io/missingauth"
151+
}
152+
Step 5: Comparing container and host IPs...
153+
VPN is active. Container IP: 172.16.88.88 (Tortuga, Rum Islands XP), Host IP: 10.42.42.42 (Port Royal, Skull Coast XP).
134154
```
135155

136156
> [!WARNING]

docker-compose.yml

Lines changed: 49 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,14 @@ services:
152152
volumes:
153153
- ${GLUETUN_CONFIG_PATH}:/gluetun:rw # Configuration files including WireGuard config at gluetun/wireguard/wg0.conf
154154

155+
# Define container healthcheck to verify VPN connectivity
156+
healthcheck:
157+
test: ["CMD-SHELL", "curl -s --max-time 10 https://ipinfo.io | grep -q 'ip'"] # Check if public IP is reachable
158+
interval: ${GLUETUN_HEALTHCHECK_INTERVAL} # Run the check at this interval
159+
timeout: ${GLUETUN_HEALTHCHECK_TIMEOUT} # Fail if it exceeds this duration
160+
start_period: ${GLUETUN_HEALTHCHECK_START_PERIOD} # Grace period before checks start
161+
retries: 5 # Mark as unhealthy after 5 failures
162+
155163
# Specify container service dependencies
156164
depends_on:
157165
privateerr: # Depends on privateer to generate the WireGuard config
@@ -194,8 +202,10 @@ services:
194202

195203
# Specify container service dependencies
196204
depends_on:
197-
- gluetun
198-
- flaresolverr
205+
gluetun:
206+
condition: service_healthy # Wait until Gluetun reports healthy VPN connection
207+
flaresolverr:
208+
condition: service_started # Ensure FlareSolverr is started
199209

200210
#
201211
# Define the 'qbittorrent' service for managing torrents
@@ -212,6 +222,11 @@ services:
212222
- ${QBITTORRENT_CONFIG_PATH}:/config:rw # Configuration files
213223
- ${HOST_DOWNLOADS_PATH}:/downloads:rw # Location of download managers output directory
214224

225+
# Specify container service dependencies
226+
depends_on:
227+
gluetun:
228+
condition: service_healthy # Wait until Gluetun reports healthy VPN connection
229+
215230
#
216231
# Define the 'radarr' service for managing movies
217232
#
@@ -230,9 +245,12 @@ services:
230245

231246
# Specify container service dependencies
232247
depends_on:
233-
- gluetun
234-
- prowlarr
235-
- qbittorrent
248+
gluetun:
249+
condition: service_healthy # Wait until Gluetun reports healthy VPN connection
250+
prowlarr:
251+
condition: service_started # Ensure Prowlarr is started
252+
qbittorrent:
253+
condition: service_started # Ensure qBittorrent is started
236254

237255
#
238256
# Define the 'sonarr' service for managing tv shows
@@ -252,9 +270,12 @@ services:
252270

253271
# Specify container service dependencies
254272
depends_on:
255-
- gluetun
256-
- prowlarr
257-
- qbittorrent
273+
gluetun:
274+
condition: service_healthy # Wait until Gluetun reports healthy VPN connection
275+
prowlarr:
276+
condition: service_started # Ensure Prowlarr is started
277+
qbittorrent:
278+
condition: service_started # Ensure qBittorrent is started
258279

259280
#
260281
# Define the 'bazarr' service for managing subtitles
@@ -274,8 +295,12 @@ services:
274295

275296
# Specify container service dependencies
276297
depends_on:
277-
- sonarr
278-
- radarr
298+
gluetun:
299+
condition: service_healthy # Wait until Gluetun reports healthy VPN connection
300+
sonarr:
301+
condition: service_started # Ensure Sonarr is started
302+
radarr:
303+
condition: service_started # Ensure Radarr is started
279304

280305
#
281306
# Define the 'readarr' service for managing ebooks
@@ -295,9 +320,12 @@ services:
295320

296321
# Specify container service dependencies
297322
depends_on:
298-
- gluetun
299-
- prowlarr
300-
- qbittorrent
323+
gluetun:
324+
condition: service_healthy # Wait until Gluetun reports healthy VPN connection
325+
prowlarr:
326+
condition: service_started # Ensure Prowlarr is started
327+
qbittorrent:
328+
condition: service_started # Ensure qBittorrent is started
301329

302330
#
303331
# Define the 'overseerr' service for managing media library requests
@@ -422,10 +450,14 @@ services:
422450

423451
# Specify container service dependencies
424452
depends_on:
425-
- gluetun
426-
- sonarr
427-
- radarr
428-
- qbittorrent
453+
gluetun:
454+
condition: service_healthy # Wait until Gluetun reports healthy VPN connection
455+
sonarr:
456+
condition: service_started # Ensure Sonarr is started
457+
radarr:
458+
condition: service_started # Ensure Radarr is started
459+
qbittorrent:
460+
condition: service_started # Ensure qBittorrent is started
429461

430462
#
431463
# Define the 'speedtest-tracker' service for tracking internet speeds

example.env

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ GLUETUN_TAG="${GLUETUN_TAG:-latest}"
7171
GLUETUN_VPN_SERVICE_PROVIDER="${GLUETUN_VPN_SERVICE_PROVIDER:-custom}"
7272
GLUETUN_VPN_TYPE="${GLUETUN_VPN_TYPE:-wireguard}"
7373
GLUETUN_CONFIG_PATH="${GLUETUN_CONFIG_PATH:-./config/gluetun}"
74+
GLUETUN_HEALTHCHECK_INTERVAL="${GLUETUN_HEALTHCHECK_INTERVAL:-10s}"
75+
GLUETUN_HEALTHCHECK_TIMEOUT="${GLUETUN_HEALTHCHECK_TIMEOUT:-10s}"
76+
GLUETUN_HEALTHCHECK_START_PERIOD="${GLUETUN_HEALTHCHECK_START_PERIOD:-10s}"
7477

7578
#
7679
# Flaresolverr environment variables

scripts/README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ To ensure ye script be running on boot, follow these steps, ye salty dogs:
4949

5050
- Click **OK** to save the task 💾.
5151

52+
---
53+
5254
### ⚙️ `entware.sh` – Summon the Tools o' the Deep
5355

5456
> [!NOTE]
@@ -147,6 +149,28 @@ To ensure the Docker fleet sets sail in the right order after every reboot, set
147149
148150
---
149151
152+
### 🕵️‍☠️ `test_vpn.sh` – Spyglass into the VPN Abyss
153+
154+
This script helps ye confirm whether yer VPN tunnel be secure and active by comparin' the public IP and location from inside a container with that o' the host.
155+
156+
**Features:**
157+
158+
- Runs a container hooked into yer VPN network (like Gluetun).
159+
- Fetches the container's public IP and location via `ipinfo.io`.
160+
- Compares it against the host's own info to spot any leaks.
161+
- Uses `jq` to pretty-print responses if installed, or falls back to a plaintext method.
162+
163+
🦜 [Unfurl the test_vpn.sh Chart](./test_vpn.sh)
164+
165+
> [!TIP]
166+
> 🔍 Useful fer confirm'n that yer containers be masked and the host be hidin' in the fog! Call it straight from the Makefile with:
167+
>
168+
> ```sh
169+
> make test-vpn
170+
> ```
171+
172+
---
173+
150174
May yer setup be swift and yer configurations flawless! 🌊🏴‍☠️
151175
152176
May this scroll guide all yer Docker fleets to set sail true and in order! ⚓🏴‍☠️

scripts/test_vpn.sh

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
#!/bin/sh
2+
#
3+
# test_vpn.sh
4+
#
5+
# Purpose:
6+
# This script verifies whether a Docker container using a VPN network
7+
# (such as with Gluetun) is routing traffic properly. It compares the
8+
# public IP and location of the container with that of the host system.
9+
#
10+
# The script:
11+
# - Runs a temporary Alpine container using the VPN network
12+
# - Installs wget inside the container
13+
# - Retrieves the container's public IP and location via ipinfo.io
14+
# - Retrieves the host's public IP and location via ipinfo.io
15+
# - Compares the two IP addresses to determine if the VPN is active
16+
# - Pretty-prints all JSON responses, using jq if available
17+
18+
# Constants
19+
CONTAINER_NAME="gluetun-latest"
20+
DOCKER_IMAGE="alpine:latest"
21+
IPINFO_URL="https://ipinfo.io"
22+
23+
# ANSI colors
24+
COLOR_RED='\033[31m'
25+
COLOR_GREEN='\033[32m'
26+
COLOR_YELLOW='\033[33m'
27+
COLOR_BLUE='\033[34m'
28+
COLOR_RESET='\033[0m'
29+
30+
# Check for jq availability
31+
if command -v jq >/dev/null 2>&1; then
32+
HAS_JQ=1
33+
else
34+
HAS_JQ=0
35+
fi
36+
37+
# Show starting message
38+
printf '%b\n' "${COLOR_BLUE}Running VPN container test...${COLOR_RESET}"
39+
40+
# Define and print the Docker command
41+
docker_cmd="docker run --rm --network=container:$CONTAINER_NAME $DOCKER_IMAGE sh -c 'apk add --no-cache wget >/dev/null 2>&1 && wget -qO- $IPINFO_URL'"
42+
printf '%b\n' "${COLOR_BLUE}Step 1: Running Docker container with VPN network:${COLOR_RESET}"
43+
printf '%b\n' "$docker_cmd"
44+
45+
# Execute the Docker command
46+
container_output=$(eval "$docker_cmd")
47+
48+
# Print container JSON output
49+
printf '%b\n' "${COLOR_BLUE}Step 2: Received container response:${COLOR_RESET}"
50+
if [ "$HAS_JQ" -eq 1 ]; then
51+
printf "%s\n" "$container_output" | jq .
52+
else
53+
printf "%s\n" "$container_output" | sed 's/[,{]/\n&/g'
54+
fi
55+
56+
# Extract container info
57+
container_ip=$(printf "%s\n" "$container_output" | grep -oE '"ip":\s*"[^"]+"' | cut -d'"' -f4)
58+
container_city=$(printf "%s\n" "$container_output" | grep -oE '"city":\s*"[^"]+"' | cut -d'"' -f4)
59+
container_region=$(printf "%s\n" "$container_output" | grep -oE '"region":\s*"[^"]+"' | cut -d'"' -f4)
60+
container_country=$(printf "%s\n" "$container_output" | grep -oE '"country":\s*"[^"]+"' | cut -d'"' -f4)
61+
container_location="$container_city, $container_region $container_country"
62+
63+
# Validate container IP
64+
if [ -z "$container_ip" ]; then
65+
printf '%b\n' "${COLOR_RED}Error: could not determine VPN container IP address.${COLOR_RESET}"
66+
exit 1
67+
fi
68+
69+
# Run and display host request
70+
printf '%b\n' "${COLOR_BLUE}Step 3: Getting host IP info from ipinfo.io...${COLOR_RESET}"
71+
host_output=$(wget -qO- "$IPINFO_URL")
72+
73+
# Print host JSON output
74+
printf '%b\n' "${COLOR_BLUE}Step 4: Received host response:${COLOR_RESET}"
75+
if [ "$HAS_JQ" -eq 1 ]; then
76+
printf "%s\n" "$host_output" | jq .
77+
else
78+
printf "%s\n" "$host_output" | sed 's/[,{]/\n&/g'
79+
fi
80+
81+
# Extract host info
82+
host_ip=$(printf "%s\n" "$host_output" | grep -oE '"ip":\s*"[^"]+"' | cut -d'"' -f4)
83+
host_city=$(printf "%s\n" "$host_output" | grep -oE '"city":\s*"[^"]+"' | cut -d'"' -f4)
84+
host_region=$(printf "%s\n" "$host_output" | grep -oE '"region":\s*"[^"]+"' | cut -d'"' -f4)
85+
host_country=$(printf "%s\n" "$host_output" | grep -oE '"country":\s*"[^"]+"' | cut -d'"' -f4)
86+
host_location="$host_city, $host_region $host_country"
87+
88+
# Compare IPs and report result
89+
printf '%b\n' "${COLOR_BLUE}Step 5: Comparing container and host IPs...${COLOR_RESET}"
90+
if [ "$container_ip" != "$host_ip" ]; then
91+
printf '%b\n' "${COLOR_GREEN}VPN is active. Container IP: $container_ip ($container_location), Host IP: $host_ip ($host_location).${COLOR_RESET}"
92+
else
93+
printf '%b\n' "${COLOR_YELLOW}Warning: container IP $container_ip matches host IP $host_ip. VPN may not be active.${COLOR_RESET}"
94+
fi

0 commit comments

Comments
 (0)