Skip to content

Instantly share code, notes, and snippets.

@9000cats
Last active March 3, 2025 02:06
Show Gist options
  • Save 9000cats/2dc03a175b095da574e6559e3acd348c to your computer and use it in GitHub Desktop.
Save 9000cats/2dc03a175b095da574e6559e3acd348c to your computer and use it in GitHub Desktop.
Wireguard & qBittorrent-nox VPN Tunnel Supervisor
#!/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