Last active
March 3, 2025 02:06
-
-
Save 9000cats/2dc03a175b095da574e6559e3acd348c to your computer and use it in GitHub Desktop.
Wireguard & qBittorrent-nox VPN Tunnel Supervisor
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # | |
# This is hard coded for Chicago servers only # | |
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # | |
# Directory containing WireGuard conf files | |
WG_DIR="/etc/wireguard" | |
# IP address to ping for connectivity checks | |
PING_IP="8.8.8.8" | |
# Time interval between pings (in seconds) | |
PING_INTERVAL=30 | |
# Number of missed pings before switching servers | |
MAX_MISSED_PINGS=3 | |
# Log file path | |
LOG_FILE="/var/log/wg_manager.log" | |
# Debug mode (1 to enable, 0 to disable) | |
DEBUG=0 | |
# qBittorrent configuration file | |
QB_CONF_FILE="/home/qbittorrent-nox/.config/qBittorrent/qBittorrent.conf" | |
# qBittorrent service name | |
QB_SERVICE="qbittorrent-nox" | |
# Function to log messages with timestamps | |
log() { | |
local message="$1" | |
local timestamp=$(date +"[%Y-%m-%d %H:%M:%S]") | |
echo "$timestamp $message" | tee -a "$LOG_FILE" | |
} | |
# Function for debug logging | |
debug_log() { | |
if [ "$DEBUG" -eq 1 ]; then | |
local message="$1" | |
local timestamp=$(date +"[%Y-%m-%d %H:%M:%S]") | |
echo "$timestamp DEBUG: $message" | tee -a "$LOG_FILE" | |
fi | |
} | |
# Function to generate list of VPN servers from configuration files | |
generate_vpn_servers() { | |
local dir="$1" | |
local pattern="$2" | |
find "$dir" -type f -name "$pattern" 2>/dev/null | sed "s#.*/##; s/\.conf$//" | |
} | |
# Generate the list of available VPN servers | |
VPN_SERVERS=($(generate_vpn_servers "$WG_DIR" "us-chi-wg-*.conf")) | |
# Variables to track current VPN connection | |
current_conf_file="" | |
current_interface="" | |
# Function to start a VPN connection | |
start_vpn() { | |
local conf_file="$1" | |
local interface=$(basename "$conf_file" .conf) | |
log "Attempting to start VPN with configuration file: $conf_file" | |
# Check if the interface already exists and is active | |
if ip link show "$interface" &> /dev/null; then | |
debug_log "Interface $interface already exists. Bringing it down..." | |
stop_vpn "$conf_file" | |
fi | |
# Attempt to bring up the VPN interface | |
wg-quick up "$interface" | |
local exit_code=$? | |
if [ "$exit_code" -ne 0 ]; then | |
log "Failed to start VPN with configuration file $conf_file. Error code: $exit_code" | |
return "$exit_code" | |
fi | |
# Verify that the interface is actually up after wg-quick | |
if ip link show "$interface" &> /dev/null; then | |
log "VPN connection established successfully using $conf_file." | |
current_conf_file="$conf_file" | |
current_interface="$interface" | |
return 0 | |
else | |
log "VPN interface not found after starting. Configuration: $conf_file" | |
return 1 | |
fi | |
} | |
# Function to stop a VPN connection | |
stop_vpn() { | |
local conf_file="$1" | |
local interface=$(basename "$conf_file" .conf) | |
local exit_code=0 | |
if ip link show "$interface" &> /dev/null; then | |
log "Stopping VPN connection: $conf_file" | |
wg-quick down "$interface" | |
exit_code=$? | |
if [ "$exit_code" -eq 0 ]; then | |
log "VPN connection stopped successfully." | |
else | |
log "Failed to stop VPN. Error code: $exit_code" | |
fi | |
# Ensure the interface is actually removed | |
while ip link show "$interface" &> /dev/null; do | |
debug_log "Waiting for interface $interface to be removed..." | |
sleep 1 | |
done | |
else | |
debug_log "Interface $interface not found. No action needed." | |
fi | |
return "$exit_code" | |
} | |
# Function to check internet connectivity by pinging a target | |
check_connectivity() { | |
local ping_ip="$1" | |
local missed_pings=0 | |
local max_missed="$MAX_MISSED_PINGS" | |
while [ "$missed_pings" -lt "$max_missed" ]; do | |
debug_log "Attempting to ping $ping_ip..." | |
if ping -c 1 "$ping_ip" &> /dev/null; then | |
debug_log "Successfully connected to $ping_ip." | |
missed_pings=0 # Reset counter on successful ping | |
return 0 | |
else | |
log "Connectivity check failed: Cannot reach $ping_ip." | |
missed_pings=$((missed_pings + 1)) | |
debug_log "Missed pings: $missed_pings / $max_missed" | |
if [ "$missed_pings" -ge "$max_missed" ]; then | |
return 1 | |
fi | |
sleep "$PING_INTERVAL" | |
fi | |
done | |
log "Maximum missed pings ($max_missed) reached. Connectivity lost." | |
return 1 | |
} | |
# Function to update qBittorrent configuration with a new interface | |
update_qbittorrent_config() { | |
local interface="$1" | |
if [ -z "$interface" ]; then | |
log "No interface provided for qBittorrent configuration update." | |
return 1 | |
fi | |
# Ensure the qBittorrent config file exists and is writable | |
if [ ! -f "$QB_CONF_FILE" ]; then | |
log "qBittorrent configuration file not found. Creating it..." | |
mkdir -p "$(dirname "$QB_CONF_FILE")" | |
touch "$QB_CONF_FILE" | |
elif [ ! -w "$QB_CONF_FILE" ]; then | |
log "qBittorrent configuration file is not writable." | |
return 1 | |
fi | |
# Update both Session\Interface and Session\InterfaceName settings | |
# Using single quotes to avoid bash interpreting the backslash | |
sed -i '/^Session\\Interface=/d' "$QB_CONF_FILE" | |
echo "Session\\Interface=$interface" >> "$QB_CONF_FILE" | |
local exit_code_sed1=$? | |
sed -i '/^Session\\InterfaceName=/d' "$QB_CONF_FILE" | |
echo "Session\\InterfaceName=$interface" >> "$QB_CONF_FILE" | |
local exit_code_sed2=$? | |
if [ "$exit_code_sed1" -eq 0 ] && [ "$exit_code_sed2" -eq 0 ]; then | |
log "qBittorrent configuration updated successfully with interface $interface." | |
return 0 | |
else | |
log "Failed to update qBittorrent configuration. Error codes: $exit_code_sed1, $exit_code_sed2" | |
return 1 | |
fi | |
} | |
# Function to restart the qBittorrent service | |
restart_qbittorrent() { | |
log "Restarting qBittorrent service..." | |
systemctl restart "$QB_SERVICE" | |
local exit_code=$? | |
if [ "$exit_code" -eq 0 ]; then | |
log "qBittorrent service restarted successfully." | |
return 0 | |
else | |
log "Failed to restart qBittorrent service. Error code: $exit_code" | |
return 1 | |
fi | |
} | |
# Function to get the current interface from qBittorrent config | |
get_current_qbittorrent_interface() { | |
if [ ! -f "$QB_CONF_FILE" ]; then | |
log "qBittorrent configuration file not found." | |
return 1 | |
fi | |
# Using single quotes to avoid shell interpretation of the backslash | |
local interface=$(grep '^Session\\Interface=' "$QB_CONF_FILE" | cut -d'=' -f2) | |
# Check if the line exists and is not empty | |
if [ -n "$interface" ]; then | |
echo "$interface" | |
return 0 | |
else | |
log "qBittorrent configuration does not contain an interface setting." | |
return 1 | |
fi | |
} | |
# Function to initialize qBittorrent configuration on script start | |
initialize_qbittorrent_config() { | |
# Get current interface from config if it exists | |
local current_interface_in_qbt="" | |
if get_current_qbittorrent_interface > /dev/null 2>&1; then | |
current_interface_in_qbt=$(get_current_qbittorrent_interface) | |
fi | |
# Check if we have a valid interface from config | |
if [ -z "$current_interface_in_qbt" ]; then | |
log "No valid interface found in qBittorrent configuration. Initializing..." | |
# Try each VPN server in order | |
for server in "${VPN_SERVERS[@]}"; do | |
local conf_file="$WG_DIR/$server.conf" | |
if [ ! -f "$conf_file" ]; then | |
log "Configuration file $conf_file not found." | |
continue | |
fi | |
# Try to start the VPN | |
start_vpn "$conf_file" | |
if [ $? -eq 0 ]; then | |
log "VPN connection established with $server." | |
# Verify connectivity | |
check_connectivity "$PING_IP" | |
if [ $? -eq 0 ]; then | |
# Update qBittorrent only if we have a valid interface | |
if [ -n "$current_interface" ]; then | |
update_qbittorrent_config "$current_interface" | |
restart_qbittorrent | |
if [ $? -eq 0 ]; then | |
log "qBittorrent configuration updated and service restarted." | |
return 0 | |
else | |
log "Failed to update qBittorrent configuration or restart service." | |
fi | |
else | |
log "No valid interface available for qBittorrent." | |
fi | |
else | |
log "VPN connection established but no internet connectivity. Stopping..." | |
stop_vpn "$conf_file" | |
fi | |
fi | |
done | |
log "No valid VPN servers available for connection." | |
return 1 | |
else | |
log "qBittorrent already configured with interface: $current_interface_in_qbt" | |
# Verify if the recorded interface is currently active | |
if [ -n "$current_interface_in_qbt" ] && ip link show "$current_interface_in_qbt" &> /dev/null; then | |
log "Interface $current_interface_in_qbt is active." | |
current_conf_file=$(find "$WG_DIR" -name "${current_interface_in_qbt}.conf") | |
current_interface="$current_interface_in_qbt" | |
return 0 | |
else | |
# Don't recursively call, just note that we need to initialize | |
log "Configured interface $current_interface_in_qbt is not active. Will initialize in main loop." | |
return 1 | |
fi | |
fi | |
} | |
# Function to cleanly stop all VPN connections and exit the script | |
cleanup_and_exit() { | |
log "Script cleanup initiated." | |
# Stop any active VPN connection first | |
if [ -n "$current_conf_file" ]; then | |
stop_vpn "$current_conf_file" | |
fi | |
# Ensure no other WireGuard interfaces are running | |
for server in "${VPN_SERVERS[@]}"; do | |
local conf_file="$WG_DIR/$server.conf" | |
if [ -f "$conf_file" ] && [ "$conf_file" != "$current_conf_file" ]; then | |
stop_vpn "$conf_file" | |
fi | |
done | |
log "All VPN connections terminated. Script exiting." | |
exit 0 | |
} | |
# Trap to handle SIGINT and SIGTERM signals for cleanup | |
trap 'cleanup_and_exit' SIGINT SIGTERM | |
# Main loop to monitor and maintain the VPN connection | |
main_loop() { | |
local last_vpn_server_index=0 | |
while true; do | |
# Cycle through each available VPN server | |
for ((server_index = 0; server_index < ${#VPN_SERVERS[@]}; server_index++)); do | |
local server="${VPN_SERVERS[$server_index]}" | |
local conf_file="$WG_DIR/$server.conf" | |
if [ ! -f "$conf_file" ]; then | |
log "Configuration file $conf_file not found. Skipping..." | |
continue | |
fi | |
# Attempt to start the VPN connection | |
start_vpn "$conf_file" | |
if [ $? -eq 0 ]; then | |
log "VPN connection established with $server." | |
# Verify connectivity after establishing the VPN | |
check_connectivity "$PING_IP" | |
if [ $? -eq 0 ]; then | |
# Ensure we have a valid interface name | |
if [ -n "$current_interface" ]; then | |
update_qbittorrent_config "$current_interface" | |
restart_qbittorrent | |
if [ $? -eq 0 ]; then | |
log "qBittorrent configuration updated and service restarted." | |
else | |
log "Failed to update qBittorrent configuration or restart service." | |
fi | |
else | |
log "No valid interface available for qBittorrent." | |
fi | |
else | |
log "VPN connection established but no internet connectivity. Stopping..." | |
stop_vpn "$conf_file" | |
continue | |
fi | |
# Continuously monitor connectivity | |
while true; do | |
check_connectivity "$PING_IP" | |
if [ $? -ne 0 ]; then | |
log "Connectivity lost. Attempting to reconnect..." | |
stop_vpn "$conf_file" | |
# Move to the next VPN server if available | |
if ((server_index < ${#VPN_SERVERS[@]} - 1)); then | |
server_index=$((server_index + 1)) | |
continue 2 # Skip to the next server in the loop | |
else | |
log "All servers exhausted. Waiting before retrying..." | |
sleep 60 | |
break | |
fi | |
else | |
# Check if qBittorrent config needs updating | |
local qb_interface="" | |
if get_current_qbittorrent_interface > /dev/null 2>&1; then | |
qb_interface=$(get_current_qbittorrent_interface) | |
if [ "$qb_interface" != "$current_interface" ]; then | |
log "qBittorrent interface ($qb_interface) differs from current VPN interface ($current_interface). Updating..." | |
update_qbittorrent_config "$current_interface" | |
restart_qbittorrent | |
fi | |
else | |
log "No interface found in qBittorrent config. Updating with current interface: $current_interface" | |
update_qbittorrent_config "$current_interface" | |
restart_qbittorrent | |
fi | |
sleep "$PING_INTERVAL" | |
fi | |
done | |
else | |
log "Failed to start VPN with server $server. Error code: $?" | |
fi | |
done | |
# If all servers have been tried, wait before retrying | |
log "All VPN servers attempted. Waiting before next attempt..." | |
sleep 60 | |
done | |
} | |
# Ensure the qBittorrent configuration file exists and is writable | |
if [ ! -f "$QB_CONF_FILE" ]; then | |
log "qBittorrent configuration file not found. Creating it..." | |
mkdir -p "$(dirname "$QB_CONF_FILE")" | |
touch "$QB_CONF_FILE" | |
elif [ ! -w "$QB_CONF_FILE" ]; then | |
log "qBittorrent configuration file is not writable." | |
exit 1 | |
fi | |
# Initialize qBittorrent configuration | |
if ! initialize_qbittorrent_config; then | |
log "Failed to initialize qBittorrent configuration. Will attempt in main loop." | |
fi | |
# Start the main loop | |
main_loop |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment