feat: initial vpndock stack
HAProxy SOCKS5 entry point + scalable purevpn-cli/microsocks exit nodes. Supports up to 10 simultaneous connections (PureVPN limit), random location selection from a predefined pool, and automatic reconnect to an unused location on server-side drop.
This commit is contained in:
16
.env.example
Normal file
16
.env.example
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# Copy to .env and fill in your details:
|
||||||
|
# cp .env.example .env
|
||||||
|
|
||||||
|
# ── PureVPN credentials ───────────────────────────────────────────────────────
|
||||||
|
PUREVPN_USER=your@email.com
|
||||||
|
PUREVPN_PASS=yourpassword
|
||||||
|
|
||||||
|
# ── Location pool (comma-separated, no spaces around commas) ─────────────────
|
||||||
|
# Each vpn-node picks randomly from this list and rotates on reconnect.
|
||||||
|
# Supports full country names or 2-letter codes — both work with purevpn-cli.
|
||||||
|
# Leave blank to use vpn-node/servers.txt instead.
|
||||||
|
#
|
||||||
|
PUREVPN_LOCATIONS=United States,United Kingdom,Germany,Netherlands,France,Canada,Australia,Singapore,Japan,Switzerland
|
||||||
|
|
||||||
|
# ── SOCKS5 listen port on the host (browser points here) ─────────────────────
|
||||||
|
SOCKS5_PORT=1080
|
||||||
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
.env
|
||||||
|
*.log
|
||||||
62
deploy.sh
Executable file
62
deploy.sh
Executable file
@@ -0,0 +1,62 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# deploy.sh – Build and scale the vpndock stack
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# ./deploy.sh → deploy 3 VPN exit nodes (default)
|
||||||
|
# ./deploy.sh 5 → deploy 5 VPN exit nodes
|
||||||
|
# ./deploy.sh 10 → deploy 10 VPN exit nodes (PureVPN's max simultaneous connections)
|
||||||
|
# ./deploy.sh 0 → tear down the stack
|
||||||
|
#
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCALE=${1:-3}
|
||||||
|
MAX_CONNECTIONS=10
|
||||||
|
COMPOSE="docker compose"
|
||||||
|
|
||||||
|
# ── Sanity checks ─────────────────────────────────────────────────────────────
|
||||||
|
if [[ ! -f .env ]]; then
|
||||||
|
echo "ERROR: .env file not found. Copy .env.example and fill in your credentials:"
|
||||||
|
echo " cp .env.example .env && nano .env"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$SCALE" -gt "$MAX_CONNECTIONS" ]]; then
|
||||||
|
echo "WARNING: PureVPN allows max $MAX_CONNECTIONS simultaneous connections."
|
||||||
|
echo " Capping scale at $MAX_CONNECTIONS."
|
||||||
|
SCALE=$MAX_CONNECTIONS
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Tear down ─────────────────────────────────────────────────────────────────
|
||||||
|
if [[ "$SCALE" -eq 0 ]]; then
|
||||||
|
echo "Tearing down vpndock stack …"
|
||||||
|
$COMPOSE down
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Build & deploy ────────────────────────────────────────────────────────────
|
||||||
|
echo "──────────────────────────────────────────────"
|
||||||
|
echo " vpndock deploy"
|
||||||
|
echo " VPN exit nodes : $SCALE"
|
||||||
|
echo " SOCKS5 port : ${SOCKS5_PORT:-1080} (on host)"
|
||||||
|
echo " Stats UI : http://localhost:8404/stats"
|
||||||
|
echo "──────────────────────────────────────────────"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Building images …"
|
||||||
|
$COMPOSE build
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Starting stack with $SCALE VPN exit node(s) …"
|
||||||
|
$COMPOSE up -d --scale vpn-node="$SCALE"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Stack is up. Waiting for containers to stabilise …"
|
||||||
|
sleep 5
|
||||||
|
$COMPOSE ps
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Done! Configure your browser's SOCKS5 proxy:"
|
||||||
|
echo " Host : $(hostname -I | awk '{print $1}')"
|
||||||
|
echo " Port : ${SOCKS5_PORT:-1080}"
|
||||||
|
echo ""
|
||||||
|
echo "HAProxy stats: http://localhost:8404/stats (admin / admin)"
|
||||||
60
docker-compose.yml
Normal file
60
docker-compose.yml
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
|
||||||
|
# ─── Entry point ───────────────────────────────────────────────────────────
|
||||||
|
haproxy:
|
||||||
|
image: haproxy:2.9-alpine
|
||||||
|
container_name: vpndock-haproxy
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "${SOCKS5_PORT:-1080}:1080" # SOCKS5 proxy for browsers/clients
|
||||||
|
- "8404:8404" # HAProxy stats UI
|
||||||
|
volumes:
|
||||||
|
- ./haproxy/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro
|
||||||
|
networks:
|
||||||
|
- proxy-net
|
||||||
|
depends_on:
|
||||||
|
- vpn-node
|
||||||
|
|
||||||
|
# ─── VPN exit nodes ────────────────────────────────────────────────────────
|
||||||
|
# Scale with: docker compose up -d --scale vpn-node=N (max 10)
|
||||||
|
vpn-node:
|
||||||
|
build:
|
||||||
|
context: ./vpn-node
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
restart: unless-stopped
|
||||||
|
cap_add:
|
||||||
|
- NET_ADMIN
|
||||||
|
devices:
|
||||||
|
- /dev/net/tun:/dev/net/tun
|
||||||
|
environment:
|
||||||
|
- PUREVPN_USER=${PUREVPN_USER}
|
||||||
|
- PUREVPN_PASS=${PUREVPN_PASS}
|
||||||
|
# Comma-separated list of locations each container picks from randomly.
|
||||||
|
# Leave blank to use vpn-node/servers.txt instead.
|
||||||
|
# Example: "United States,United Kingdom,Germany,Netherlands,France"
|
||||||
|
- PUREVPN_LOCATIONS=${PUREVPN_LOCATIONS:-}
|
||||||
|
- SOCKS5_INNER_PORT=1080
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
networks:
|
||||||
|
- proxy-net
|
||||||
|
expose:
|
||||||
|
- "1080"
|
||||||
|
sysctls:
|
||||||
|
- net.ipv4.conf.all.rp_filter=2
|
||||||
|
- net.ipv6.conf.all.disable_ipv6=1
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "nc -z 127.0.0.1 1080 || exit 1"]
|
||||||
|
interval: 20s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
start_period: 45s
|
||||||
|
|
||||||
|
networks:
|
||||||
|
proxy-net:
|
||||||
|
driver: bridge
|
||||||
|
ipam:
|
||||||
|
config:
|
||||||
|
- subnet: 172.28.0.0/24
|
||||||
55
haproxy/haproxy.cfg
Normal file
55
haproxy/haproxy.cfg
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
global
|
||||||
|
log stdout format raw local0 info
|
||||||
|
maxconn 50000
|
||||||
|
# Run as non-root inside the container
|
||||||
|
# user haproxy
|
||||||
|
# group haproxy
|
||||||
|
|
||||||
|
defaults
|
||||||
|
log global
|
||||||
|
mode tcp
|
||||||
|
option tcplog
|
||||||
|
option dontlognull
|
||||||
|
retries 3
|
||||||
|
timeout connect 10s
|
||||||
|
timeout client 1m
|
||||||
|
timeout server 1m
|
||||||
|
timeout check 5s
|
||||||
|
|
||||||
|
# ── Docker embedded DNS ───────────────────────────────────────────────────────
|
||||||
|
resolvers docker_dns
|
||||||
|
nameserver dns1 127.0.0.11:53
|
||||||
|
resolve_retries 10
|
||||||
|
timeout resolve 1s
|
||||||
|
timeout retry 1s
|
||||||
|
hold valid 10s
|
||||||
|
hold other 10s
|
||||||
|
hold refused 10s
|
||||||
|
hold nx 10s
|
||||||
|
|
||||||
|
# ── SOCKS5 frontend (browsers / curl / etc. connect here) ────────────────────
|
||||||
|
frontend socks5_in
|
||||||
|
bind *:1080
|
||||||
|
default_backend vpn_exit_nodes
|
||||||
|
|
||||||
|
# ── Round-robin across all vpn-node containers ───────────────────────────────
|
||||||
|
backend vpn_exit_nodes
|
||||||
|
balance roundrobin
|
||||||
|
# server-template creates up to 10 slots; Docker DNS fills them dynamically
|
||||||
|
# as you scale with: docker compose up --scale vpn-node=N (max 10)
|
||||||
|
server-template vpn 1-10 vpn-node:1080 \
|
||||||
|
resolvers docker_dns \
|
||||||
|
resolve-prefer ipv4 \
|
||||||
|
init-addr none \
|
||||||
|
check inter 20s fall 2 rise 2
|
||||||
|
|
||||||
|
# ── Stats page — http://<host>:8404/stats ─────────────────────────────────────
|
||||||
|
frontend stats
|
||||||
|
bind *:8404
|
||||||
|
mode http
|
||||||
|
stats enable
|
||||||
|
stats uri /stats
|
||||||
|
stats refresh 5s
|
||||||
|
stats show-legends
|
||||||
|
stats show-node
|
||||||
|
stats auth admin:admin # change this password
|
||||||
40
vpn-node/Dockerfile
Normal file
40
vpn-node/Dockerfile
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
FROM debian:bookworm-slim
|
||||||
|
|
||||||
|
LABEL description="microsocks + purevpn-cli exit node"
|
||||||
|
|
||||||
|
# ── System dependencies ───────────────────────────────────────────────────────
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
gcc make git \
|
||||||
|
curl wget ca-certificates \
|
||||||
|
iproute2 iptables iputils-ping \
|
||||||
|
netcat-openbsd procps dnsutils \
|
||||||
|
expect \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# ── Install purevpn-cli (official installer) ──────────────────────────────────
|
||||||
|
# Running as root inside Docker so no sudo needed.
|
||||||
|
RUN curl -fsSL https://apps.purevpn-tools.com/cross-platform/linux-cli/production/cli-install.sh \
|
||||||
|
-o /tmp/cli-install.sh \
|
||||||
|
&& bash /tmp/cli-install.sh \
|
||||||
|
&& rm -f /tmp/cli-install.sh
|
||||||
|
|
||||||
|
# ── Add purevpn-cli to PATH (as per official docs) ────────────────────────────
|
||||||
|
ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin:/etc/pure-linux-cli/
|
||||||
|
|
||||||
|
# ── Build microsocks from source ──────────────────────────────────────────────
|
||||||
|
RUN git clone --depth 1 https://github.com/rofl0r/microsocks.git /tmp/microsocks \
|
||||||
|
&& cd /tmp/microsocks \
|
||||||
|
&& make \
|
||||||
|
&& cp microsocks /usr/local/bin/microsocks \
|
||||||
|
&& rm -rf /tmp/microsocks
|
||||||
|
|
||||||
|
# ── Location list ─────────────────────────────────────────────────────────────
|
||||||
|
COPY servers.txt /etc/vpndock/servers.txt
|
||||||
|
|
||||||
|
# ── Entrypoint ────────────────────────────────────────────────────────────────
|
||||||
|
COPY entrypoint.sh /entrypoint.sh
|
||||||
|
RUN chmod +x /entrypoint.sh
|
||||||
|
|
||||||
|
EXPOSE 1080
|
||||||
|
|
||||||
|
ENTRYPOINT ["/entrypoint.sh"]
|
||||||
214
vpn-node/entrypoint.sh
Executable file
214
vpn-node/entrypoint.sh
Executable file
@@ -0,0 +1,214 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# entrypoint.sh
|
||||||
|
# Starts purevpn-cli (randomly selected location, rotates on reconnect)
|
||||||
|
# then starts microsocks bound to 0.0.0.0 so HAProxy can reach it.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin:/etc/pure-linux-cli/
|
||||||
|
|
||||||
|
SOCKS5_PORT="${SOCKS5_INNER_PORT:-1080}"
|
||||||
|
VPN_IF="tun0"
|
||||||
|
VPN_WAIT=60 # max seconds to wait for tun0 to appear
|
||||||
|
|
||||||
|
# ── Logging ───────────────────────────────────────────────────────────────────
|
||||||
|
log() { echo "[$(date '+%H:%M:%S')] [$(hostname)] $*"; }
|
||||||
|
die() { log "FATAL: $*" >&2; exit 1; }
|
||||||
|
|
||||||
|
# ── Build location pool ───────────────────────────────────────────────────────
|
||||||
|
# Priority: env var PUREVPN_LOCATIONS (comma-separated) > servers.txt
|
||||||
|
declare -a ALL_LOCATIONS
|
||||||
|
if [[ -n "${PUREVPN_LOCATIONS:-}" ]]; then
|
||||||
|
IFS=',' read -ra ALL_LOCATIONS <<< "$PUREVPN_LOCATIONS"
|
||||||
|
# Trim whitespace from each element
|
||||||
|
for i in "${!ALL_LOCATIONS[@]}"; do
|
||||||
|
ALL_LOCATIONS[$i]="${ALL_LOCATIONS[$i]#"${ALL_LOCATIONS[$i]%%[![:space:]]*}"}"
|
||||||
|
ALL_LOCATIONS[$i]="${ALL_LOCATIONS[$i]%"${ALL_LOCATIONS[$i]##*[![:space:]]}"}"
|
||||||
|
done
|
||||||
|
else
|
||||||
|
mapfile -t ALL_LOCATIONS < <(
|
||||||
|
grep -v '^\s*#' /etc/vpndock/servers.txt | grep -v '^\s*$'
|
||||||
|
)
|
||||||
|
fi
|
||||||
|
|
||||||
|
[[ ${#ALL_LOCATIONS[@]} -eq 0 ]] && die "No locations found. Set PUREVPN_LOCATIONS or populate servers.txt"
|
||||||
|
[[ -z "${PUREVPN_USER:-}" ]] && die "PUREVPN_USER is not set"
|
||||||
|
[[ -z "${PUREVPN_PASS:-}" ]] && die "PUREVPN_PASS is not set"
|
||||||
|
|
||||||
|
log "Location pool (${#ALL_LOCATIONS[@]}): ${ALL_LOCATIONS[*]}"
|
||||||
|
|
||||||
|
# ── Rotation state ────────────────────────────────────────────────────────────
|
||||||
|
declare -a USED_LOCATIONS=()
|
||||||
|
CURRENT_LOCATION=""
|
||||||
|
VPN_PID=""
|
||||||
|
SOCKS_PID=""
|
||||||
|
|
||||||
|
# Pick a random location that hasn't been used yet.
|
||||||
|
# Resets the used list when all locations are exhausted.
|
||||||
|
pick_location() {
|
||||||
|
local available=()
|
||||||
|
for loc in "${ALL_LOCATIONS[@]}"; do
|
||||||
|
local already_used=false
|
||||||
|
for used in "${USED_LOCATIONS[@]:-}"; do
|
||||||
|
[[ "$loc" == "$used" ]] && already_used=true && break
|
||||||
|
done
|
||||||
|
$already_used || available+=("$loc")
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ ${#available[@]} -eq 0 ]]; then
|
||||||
|
log "All locations tried — resetting rotation pool"
|
||||||
|
USED_LOCATIONS=()
|
||||||
|
available=("${ALL_LOCATIONS[@]}")
|
||||||
|
fi
|
||||||
|
|
||||||
|
local idx=$(( RANDOM % ${#available[@]} ))
|
||||||
|
echo "${available[$idx]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── iptables: let HAProxy reach microsocks despite VPN kill-switch ────────────
|
||||||
|
whitelist_eth0() {
|
||||||
|
local ip
|
||||||
|
ip=$(ip -4 addr show eth0 2>/dev/null | grep -oP '(?<=inet\s)\d+(\.\d+){3}' || true)
|
||||||
|
if [[ -n "$ip" ]]; then
|
||||||
|
iptables -I INPUT -i eth0 -j ACCEPT 2>/dev/null || true
|
||||||
|
iptables -I OUTPUT -o eth0 -j ACCEPT 2>/dev/null || true
|
||||||
|
log "eth0 ($ip) whitelisted — HAProxy can reach microsocks"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Non-interactive login via expect ─────────────────────────────────────────
|
||||||
|
do_login() {
|
||||||
|
log "Logging into PureVPN …"
|
||||||
|
expect -c "
|
||||||
|
set timeout 30
|
||||||
|
spawn purevpn-cli --login
|
||||||
|
expect -re {[Ee]mail|[Uu]sername|[Ll]ogin} { send \"$PUREVPN_USER\r\" }
|
||||||
|
expect -re {[Pp]assword} { send \"$PUREVPN_PASS\r\" }
|
||||||
|
expect eof
|
||||||
|
" && log "Login OK" || {
|
||||||
|
# Fallback: pipe credentials (works on some CLI versions)
|
||||||
|
log "expect login failed, trying stdin …"
|
||||||
|
printf '%s\n%s\n' "$PUREVPN_USER" "$PUREVPN_PASS" | purevpn-cli --login || true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Disconnect any existing VPN session ──────────────────────────────────────
|
||||||
|
vpn_disconnect() {
|
||||||
|
purevpn-cli --disconnect 2>/dev/null || true
|
||||||
|
[[ -n "$VPN_PID" ]] && kill "$VPN_PID" 2>/dev/null || true
|
||||||
|
VPN_PID=""
|
||||||
|
# Give the tunnel time to tear down
|
||||||
|
sleep 3
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Connect to a new (random, unused) location ───────────────────────────────
|
||||||
|
vpn_connect() {
|
||||||
|
CURRENT_LOCATION="$(pick_location)"
|
||||||
|
USED_LOCATIONS+=("$CURRENT_LOCATION")
|
||||||
|
log "Connecting → '$CURRENT_LOCATION' (used so far: ${USED_LOCATIONS[*]})"
|
||||||
|
purevpn-cli --connect "$CURRENT_LOCATION" &
|
||||||
|
VPN_PID=$!
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Wait for tun0 to appear; return 1 on timeout ────────────────────────────
|
||||||
|
wait_for_tunnel() {
|
||||||
|
local waited=0
|
||||||
|
while ! ip link show "$VPN_IF" &>/dev/null; do
|
||||||
|
sleep 2; waited=$(( waited + 2 ))
|
||||||
|
[[ $waited -ge $VPN_WAIT ]] && return 1
|
||||||
|
done
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Start / restart microsocks ────────────────────────────────────────────────
|
||||||
|
start_socks() {
|
||||||
|
[[ -n "$SOCKS_PID" ]] && kill "$SOCKS_PID" 2>/dev/null || true
|
||||||
|
log "Starting microsocks on 0.0.0.0:${SOCKS5_PORT}"
|
||||||
|
microsocks -p "$SOCKS5_PORT" &
|
||||||
|
SOCKS_PID=$!
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Connect with automatic fallback to next location on failure ───────────────
|
||||||
|
connect_with_fallback() {
|
||||||
|
local attempts=0
|
||||||
|
local max_attempts=${#ALL_LOCATIONS[@]}
|
||||||
|
|
||||||
|
while [[ $attempts -lt $max_attempts ]]; do
|
||||||
|
vpn_connect
|
||||||
|
if wait_for_tunnel; then
|
||||||
|
local ext_ip
|
||||||
|
ext_ip=$(curl -sf --max-time 8 https://api4.my-ip.io/ip || echo "unknown")
|
||||||
|
log "VPN up — location: '$CURRENT_LOCATION' | exit IP: $ext_ip"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
log "'$CURRENT_LOCATION' timed out after ${VPN_WAIT}s — trying next location …"
|
||||||
|
vpn_disconnect
|
||||||
|
attempts=$(( attempts + 1 ))
|
||||||
|
done
|
||||||
|
|
||||||
|
die "Could not connect to any location after $attempts attempts"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
# Main
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
whitelist_eth0
|
||||||
|
do_login
|
||||||
|
connect_with_fallback
|
||||||
|
start_socks
|
||||||
|
|
||||||
|
# ── Reconnect helper (shared by monitor triggers) ────────────────────────────
|
||||||
|
do_reconnect() {
|
||||||
|
local reason="$1"
|
||||||
|
log "Reconnecting — reason: $reason (was '$CURRENT_LOCATION')"
|
||||||
|
|
||||||
|
kill "$SOCKS_PID" 2>/dev/null || true
|
||||||
|
SOCKS_PID=""
|
||||||
|
|
||||||
|
vpn_disconnect
|
||||||
|
connect_with_fallback
|
||||||
|
start_socks
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Tunnel liveness: interface up AND traffic actually flows ──────────────────
|
||||||
|
# Returns 0 if healthy, 1 if dead/stalled.
|
||||||
|
tunnel_alive() {
|
||||||
|
# 1. Interface must exist
|
||||||
|
ip link show "$VPN_IF" &>/dev/null || return 1
|
||||||
|
|
||||||
|
# 2. purevpn-cli process must still be running
|
||||||
|
[[ -n "$VPN_PID" ]] && kill -0 "$VPN_PID" 2>/dev/null || return 1
|
||||||
|
|
||||||
|
# 3. Traffic must actually flow through the tunnel (ghost-interface check).
|
||||||
|
# Sends 2 pings via the VPN interface with a 5-second timeout each.
|
||||||
|
ping -c 2 -W 5 -I "$VPN_IF" 1.1.1.1 &>/dev/null || return 1
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Monitor loop ──────────────────────────────────────────────────────────────
|
||||||
|
log "Entering monitor loop (checks every 15s) …"
|
||||||
|
FAIL_COUNT=0
|
||||||
|
MAX_FAILS=2 # consecutive failed checks before reconnecting
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
sleep 15
|
||||||
|
|
||||||
|
# ── VPN health check (interface + process + traffic) ─────────────────────
|
||||||
|
if ! tunnel_alive; then
|
||||||
|
FAIL_COUNT=$(( FAIL_COUNT + 1 ))
|
||||||
|
log "Health check failed ($FAIL_COUNT/$MAX_FAILS) for '$CURRENT_LOCATION'"
|
||||||
|
|
||||||
|
if [[ $FAIL_COUNT -ge $MAX_FAILS ]]; then
|
||||||
|
FAIL_COUNT=0
|
||||||
|
do_reconnect "server drop / tunnel stalled"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
FAIL_COUNT=0 # reset on any healthy check
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── microsocks process check ──────────────────────────────────────────────
|
||||||
|
if ! kill -0 "$SOCKS_PID" 2>/dev/null; then
|
||||||
|
log "microsocks died — restarting"
|
||||||
|
start_socks
|
||||||
|
fi
|
||||||
|
done
|
||||||
17
vpn-node/servers.txt
Normal file
17
vpn-node/servers.txt
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# PureVPN locations — one per line
|
||||||
|
# Use country name ("United States") or 2-letter code ("US") — both work with:
|
||||||
|
# purevpn-cli --connect "United States" OR purevpn-cli -c "US"
|
||||||
|
#
|
||||||
|
# Run `purevpn-cli --list` to see all available locations after installing.
|
||||||
|
# Lines starting with # are ignored.
|
||||||
|
|
||||||
|
United States
|
||||||
|
United Kingdom
|
||||||
|
Germany
|
||||||
|
Netherlands
|
||||||
|
France
|
||||||
|
Canada
|
||||||
|
Australia
|
||||||
|
Singapore
|
||||||
|
Japan
|
||||||
|
Switzerland
|
||||||
Reference in New Issue
Block a user