mirror of
https://github.com/alexbelgium/hassio-addons.git
synced 2026-01-10 09:51:02 +01:00
Refactor VPN and WireGuard handling in run script
Refactor VPN leak monitoring and WireGuard setup logic, improving clarity and functionality. Adjust log messages for better understanding.
This commit is contained in:
@@ -2,16 +2,16 @@
|
||||
# shellcheck shell=bash
|
||||
|
||||
WEBUI_PORT=${WEBUI_PORT:-8080}
|
||||
|
||||
export PATH="/usr/local/sbin:/usr/local/bin:${PATH}"
|
||||
|
||||
# Global variable used by WireGuard bring-up helper
|
||||
output=""
|
||||
# --- Configuration & Pre-checks ---
|
||||
|
||||
if bashio::config.true 'silent'; then
|
||||
sed -i 's|/proc/1/fd/1 hassio;|off;|g' /etc/nginx/nginx.conf
|
||||
fi
|
||||
|
||||
# --- Helper Functions ---
|
||||
|
||||
_fetch_public_ip() {
|
||||
local resp
|
||||
local url
|
||||
@@ -70,18 +70,13 @@ _fetch_country_code() {
|
||||
}
|
||||
|
||||
_vpn_monitor_public_ip() {
|
||||
# Arg1: label ("OpenVPN", "WireGuard", etc.)
|
||||
local vpn_label="${1:-VPN}"
|
||||
local current_ip_file="/currentip"
|
||||
local baseline_ip
|
||||
local vpn_ip
|
||||
local country
|
||||
local interval
|
||||
local initial_delay
|
||||
|
||||
interval=${VPN_LEAK_CHECK_INTERVAL:-300}
|
||||
initial_delay=${VPN_LEAK_INITIAL_DELAY:-60}
|
||||
local baseline_ip vpn_ip country
|
||||
local interval=${VPN_LEAK_CHECK_INTERVAL:-300}
|
||||
local initial_delay=${VPN_LEAK_INITIAL_DELAY:-60}
|
||||
|
||||
# Pre-flight checks
|
||||
if ! command -v curl >/dev/null 2>&1; then
|
||||
bashio::log.warning "${vpn_label}: curl not found; VPN leak monitoring disabled."
|
||||
return 0
|
||||
@@ -98,39 +93,145 @@ _vpn_monitor_public_ip() {
|
||||
return 0
|
||||
fi
|
||||
|
||||
bashio::log.info "${vpn_label}: baseline (non-VPN) IP for leak detection: ${baseline_ip}"
|
||||
bashio::log.debug "${vpn_label}: waiting ${initial_delay}s before first leak check."
|
||||
bashio::log.info "${vpn_label}: Baseline (ISP) IP: ${baseline_ip}"
|
||||
bashio::log.debug "${vpn_label}: Waiting ${initial_delay}s before first leak check."
|
||||
sleep "${initial_delay}"
|
||||
|
||||
while true; do
|
||||
vpn_ip="$(_fetch_public_ip || true)"
|
||||
|
||||
if [[ -z "${vpn_ip}" ]]; then
|
||||
bashio::log.warning "${vpn_label}: failed to obtain VPN public IP from all providers (rate limited or unreachable)."
|
||||
bashio::log.warning "${vpn_label}: Failed to fetch public IP (rate limited or connection down)."
|
||||
else
|
||||
if country="$(_fetch_country_code || true)"; then
|
||||
bashio::log.info "${vpn_label}: public IP: ${vpn_ip} (${country})."
|
||||
bashio::log.info "${vpn_label}: Current IP: ${vpn_ip} (${country})"
|
||||
else
|
||||
bashio::log.info "${vpn_label}: public IP: ${vpn_ip} (country unknown)."
|
||||
bashio::log.info "${vpn_label}: Current IP: ${vpn_ip} (Country Unknown)"
|
||||
fi
|
||||
|
||||
# LEAK DETECTED
|
||||
if [[ "${vpn_ip}" == "${baseline_ip}" ]]; then
|
||||
bashio::log.error "${vpn_label}: VPN leak detected: public IP ${vpn_ip} matches baseline ${baseline_ip}. Stopping add-on."
|
||||
# Try to terminate the service tree cleanly.
|
||||
bashio::log.fatal "${vpn_label}: VPN LEAK DETECTED! Current IP ${vpn_ip} matches baseline. Stopping container."
|
||||
s6-svscanctl -t /var/run/s6/services 2>/dev/null || true
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
sleep "${interval}"
|
||||
done
|
||||
}
|
||||
|
||||
#
|
||||
# --- Main logic: OpenVPN / WireGuard / qBittorrent ---
|
||||
#
|
||||
# --- WireGuard Specific Logic ---
|
||||
|
||||
_setup_wireguard() {
|
||||
local WIREGUARD_STATE_DIR="/var/run/wireguard"
|
||||
local output=""
|
||||
local status=0
|
||||
|
||||
if ! bashio::fs.file_exists "${WIREGUARD_STATE_DIR}/config"; then
|
||||
bashio::exit.nok 'WireGuard runtime configuration not prepared. Please restart the add-on.'
|
||||
fi
|
||||
|
||||
local wireguard_config
|
||||
wireguard_config="$(cat "${WIREGUARD_STATE_DIR}/config")"
|
||||
local wireguard_interface
|
||||
wireguard_interface="$(cat "${WIREGUARD_STATE_DIR}/interface" 2>/dev/null || echo 'wg0')"
|
||||
|
||||
if ip link show "${wireguard_interface}" &> /dev/null; then
|
||||
bashio::log.warning "WireGuard interface ${wireguard_interface} already exists. Resetting."
|
||||
wg-quick down "${wireguard_config}" >/dev/null 2>&1 || true
|
||||
fi
|
||||
|
||||
bashio::log.info "Starting WireGuard interface ${wireguard_interface}..."
|
||||
|
||||
# Internal helper: fallback for iptables-legacy
|
||||
_wg_prepare_legacy() {
|
||||
local legacy_bin_dir="${WIREGUARD_STATE_DIR}/iptables-legacy-bin"
|
||||
mkdir -p "${legacy_bin_dir}"
|
||||
local cmd
|
||||
for cmd in iptables iptables-save iptables-restore ip6tables ip6tables-save ip6tables-restore; do
|
||||
if command -v "${cmd}-legacy" >/dev/null 2>&1; then
|
||||
ln -sf "$(command -v "${cmd}-legacy")" "${legacy_bin_dir}/${cmd}"
|
||||
fi
|
||||
done
|
||||
chmod 700 "${legacy_bin_dir}" 2>/dev/null || true
|
||||
export PATH="${legacy_bin_dir}:${PATH}"
|
||||
bashio::log.warning 'Retrying WireGuard using iptables-legacy wrappers.'
|
||||
}
|
||||
|
||||
# Internal helper: Attempt connection
|
||||
_wg_up_attempt() {
|
||||
local config_path="$1"
|
||||
output="$(wg-quick up "${config_path}" 2>&1)" || status=$?
|
||||
|
||||
if [ "${status}" -eq 0 ]; then return 0; fi
|
||||
|
||||
# Check for iptables errors and try legacy fallback
|
||||
if echo "${output}" | grep -qiE 'iptables-restore|ip6tables-restore|xtables'; then
|
||||
if command -v iptables-legacy >/dev/null 2>&1; then
|
||||
wg-quick down "${config_path}" >/dev/null 2>&1 || true
|
||||
_wg_prepare_legacy
|
||||
output="$(wg-quick up "${config_path}" 2>&1)" || status=$?
|
||||
else
|
||||
bashio::log.warning 'iptables errors detected but iptables-legacy missing.'
|
||||
status=1
|
||||
fi
|
||||
fi
|
||||
return "${status}"
|
||||
}
|
||||
|
||||
# 1. First Attempt
|
||||
if ! _wg_up_attempt "${wireguard_config}"; then
|
||||
bashio::log.warning 'Initial WireGuard connection failed. Trying IPv4-only endpoints.'
|
||||
bashio::log.debug "Output: ${output}"
|
||||
|
||||
# 2. IPv4 Fallback Preparation
|
||||
local ipv4_config="${WIREGUARD_STATE_DIR}/${wireguard_interface}-ipv4.conf"
|
||||
: > "${ipv4_config}"
|
||||
chmod 600 "${ipv4_config}" 2>/dev/null || true
|
||||
|
||||
local line endpoint endpoint_host endpoint_port
|
||||
while IFS= read -r line || [ -n "$line" ]; do
|
||||
if [[ "${line}" =~ ^Endpoint ]]; then
|
||||
endpoint="${line#Endpoint = }"
|
||||
endpoint_host="${endpoint%:*}"
|
||||
endpoint_port="${endpoint##*:}"
|
||||
|
||||
# Resolve hostname to IPv4
|
||||
mapfile -t ipv4_candidates < <(getent ahostsv4 "${endpoint_host}" | awk '{print $1}' | uniq)
|
||||
|
||||
if [ ${#ipv4_candidates[@]} -gt 0 ]; then
|
||||
bashio::log.debug "Resolved ${endpoint_host} to ${ipv4_candidates[0]}"
|
||||
echo "Endpoint = ${ipv4_candidates[0]}:${endpoint_port}" >> "${ipv4_config}"
|
||||
else
|
||||
echo "${line}" >> "${ipv4_config}"
|
||||
fi
|
||||
else
|
||||
echo "${line}" >> "${ipv4_config}"
|
||||
fi
|
||||
done < "${wireguard_config}"
|
||||
|
||||
wg-quick down "${wireguard_config}" >/dev/null 2>&1 || true
|
||||
|
||||
# 3. Second Attempt (IPv4 only)
|
||||
if ! _wg_up_attempt "${ipv4_config}"; then
|
||||
bashio::log.error 'WireGuard failed to establish connection.'
|
||||
bashio::log.error "${output}"
|
||||
bashio::exit.nok 'WireGuard start failed.'
|
||||
fi
|
||||
fi
|
||||
|
||||
bashio::log.info "WireGuard interface ${wireguard_interface} is up."
|
||||
|
||||
# DNS Refresh
|
||||
if command -v resolvconf >/dev/null 2>&1; then
|
||||
resolvconf -u >/dev/null 2>&1 || bashio::log.warning 'resolvconf -u failed.'
|
||||
fi
|
||||
}
|
||||
|
||||
# --- Main Execution ---
|
||||
|
||||
if bashio::config.true 'openvpn_enabled'; then
|
||||
# Start leak monitor for OpenVPN in the background
|
||||
# Start Leak Monitor
|
||||
_vpn_monitor_public_ip "OpenVPN" &
|
||||
|
||||
exec /usr/sbin/openvpn \
|
||||
@@ -143,134 +244,27 @@ if bashio::config.true 'openvpn_enabled'; then
|
||||
--pull-filter ignore "tun-ipv6" \
|
||||
--pull-filter ignore "redirect-gateway ipv6" \
|
||||
--pull-filter ignore "dhcp-option DNS6"
|
||||
else
|
||||
if bashio::config.true 'wireguard_enabled'; then
|
||||
WIREGUARD_STATE_DIR="/var/run/wireguard"
|
||||
|
||||
if ! bashio::fs.file_exists "${WIREGUARD_STATE_DIR}/config"; then
|
||||
bashio::exit.nok 'WireGuard runtime configuration not prepared. Please restart the add-on.'
|
||||
fi
|
||||
elif bashio::config.true 'wireguard_enabled'; then
|
||||
|
||||
# Run modularized WireGuard setup
|
||||
_setup_wireguard
|
||||
|
||||
wireguard_config="$(cat "${WIREGUARD_STATE_DIR}/config")"
|
||||
wireguard_interface="$(cat "${WIREGUARD_STATE_DIR}/interface" 2>/dev/null || echo 'wg0')"
|
||||
# Start Leak Monitor
|
||||
_vpn_monitor_public_ip "WireGuard" &
|
||||
|
||||
if ip link show "${wireguard_interface}" &> /dev/null; then
|
||||
bashio::log.warning "WireGuard interface ${wireguard_interface} already exists. Attempting to reset it."
|
||||
wg-quick down "${wireguard_config}" >/dev/null 2>&1 || true
|
||||
fi
|
||||
|
||||
bashio::log.info "Starting WireGuard interface ${wireguard_interface} using ${wireguard_config##*/}."
|
||||
|
||||
# Prefer host-provided iptables-legacy binaries if the default backend fails.
|
||||
_wireguard_prepare_iptables_legacy() {
|
||||
local legacy_bin_dir="${WIREGUARD_STATE_DIR}/iptables-legacy-bin"
|
||||
mkdir -p "${legacy_bin_dir}"
|
||||
|
||||
local cmd
|
||||
for cmd in iptables iptables-save iptables-restore ip6tables ip6tables-save ip6tables-restore; do
|
||||
if command -v "${cmd}-legacy" >/dev/null 2>&1; then
|
||||
ln -sf "$(command -v "${cmd}-legacy")" "${legacy_bin_dir}/${cmd}"
|
||||
fi
|
||||
done
|
||||
|
||||
chmod 700 "${legacy_bin_dir}" 2>/dev/null || true
|
||||
export PATH="${legacy_bin_dir}:${PATH}"
|
||||
bashio::log.warning 'Retrying WireGuard bring-up using iptables-legacy wrappers.'
|
||||
}
|
||||
|
||||
_wireguard_up_with_iptables_fallback() {
|
||||
local config_path="$1"
|
||||
local status=0
|
||||
|
||||
output="$(wg-quick up "${config_path}" 2>&1)" || status=$?
|
||||
|
||||
if [ "${status}" -eq 0 ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if echo "${output}" | grep -qiE 'iptables-restore|ip6tables-restore|xtables'; then
|
||||
if command -v iptables-legacy >/dev/null 2>&1 || command -v ip6tables-legacy >/dev/null 2>&1; then
|
||||
wg-quick down "${config_path}" >/dev/null 2>&1 || true
|
||||
_wireguard_prepare_iptables_legacy
|
||||
output="$(wg-quick up "${config_path}" 2>&1)" || status=$?
|
||||
else
|
||||
bashio::log.warning 'iptables errors detected but iptables-legacy binaries are unavailable in the image.'
|
||||
status=1
|
||||
fi
|
||||
fi
|
||||
|
||||
return "${status}"
|
||||
}
|
||||
|
||||
if ! _wireguard_up_with_iptables_fallback "${wireguard_config}"; then
|
||||
bashio::log.warning 'Initial WireGuard connection attempt failed. Trying again with IPv4-only endpoints.'
|
||||
bashio::log.warning "First attempt output:${bashio::constants.LF}${output}"
|
||||
|
||||
ipv4_config="${WIREGUARD_STATE_DIR}/${wireguard_interface}-ipv4.conf"
|
||||
: > "${ipv4_config}"
|
||||
chmod 600 "${ipv4_config}" 2>/dev/null || true
|
||||
|
||||
local line endpoint endpoint_host endpoint_port
|
||||
while IFS= read -r line; do
|
||||
if [[ "${line}" =~ ^Endpoint ]]; then
|
||||
endpoint="${line#Endpoint = }"
|
||||
endpoint_host="${endpoint%:*}"
|
||||
endpoint_port="${endpoint##*:}"
|
||||
|
||||
mapfile -t ipv4_candidates < <(getent ahostsv4 "${endpoint_host}" | awk '{print $1}' | uniq)
|
||||
|
||||
if [ ${#ipv4_candidates[@]} -gt 0 ]; then
|
||||
bashio::log.debug "Resolved ${endpoint_host} to IPv4 address ${ipv4_candidates[0]} for WireGuard fallback."
|
||||
echo "Endpoint = ${ipv4_candidates[0]}:${endpoint_port}" >> "${ipv4_config}"
|
||||
else
|
||||
bashio::log.warning "No IPv4 address found for ${endpoint_host}. Keeping original endpoint for fallback."
|
||||
echo "${line}" >> "${ipv4_config}"
|
||||
fi
|
||||
else
|
||||
echo "${line}" >> "${ipv4_config}"
|
||||
fi
|
||||
done < "${wireguard_config}"
|
||||
|
||||
wg-quick down "${wireguard_config}" >/dev/null 2>&1 || true
|
||||
|
||||
if ! _wireguard_up_with_iptables_fallback "${ipv4_config}"; then
|
||||
bashio::log.error 'WireGuard failed to establish a connection after IPv4-only retry.'
|
||||
bashio::log.error "wg-quick output:"
|
||||
bashio::log.error "${output}"
|
||||
bashio::log.error 'Troubleshooting steps:'
|
||||
bashio::log.error " 1. Confirm that the WireGuard configuration file '${wireguard_config}' exists inside the container and contains valid private/public keys, endpoint and AllowedIPs."
|
||||
bashio::log.error ' 2. Ensure UDP port 51820 (or the port defined in your config) is forwarded on your router to this host and not blocked by your firewall or ISP.'
|
||||
bashio::log.error ' 3. Verify that the configured endpoint (IP/hostname and port) is reachable from this container (e.g. ping or nc from a debug shell).'
|
||||
bashio::log.error ' 4. Check that the system time is correct (NTP); large time drift can break key handshakes.'
|
||||
bashio::log.error ' 5. Confirm that WireGuard kernel support / module is available in the host system.'
|
||||
bashio::log.error ' 6. If DNS names are used for the endpoint, verify DNS resolution from inside the container (e.g. nslookup or dig).'
|
||||
bashio::exit.nok 'WireGuard start failed. See the log above for details.'
|
||||
fi
|
||||
fi
|
||||
|
||||
bashio::log.info "WireGuard interface ${wireguard_interface} is up."
|
||||
|
||||
# Refresh DNS resolver configuration if resolvconf is present
|
||||
if command -v resolvconf >/dev/null 2>&1; then
|
||||
bashio::log.info 'Refreshing DNS resolver configuration via resolvconf -u.'
|
||||
if ! resolvconf -u >/dev/null 2>&1; then
|
||||
bashio::log.warning 'resolvconf -u failed. DNS configuration may not have been updated.'
|
||||
fi
|
||||
else
|
||||
bashio::log.debug 'resolvconf not found in PATH; skipping DNS refresh.'
|
||||
fi
|
||||
|
||||
# Start VPN leak monitor for WireGuard in background
|
||||
_vpn_monitor_public_ip "WireGuard" &
|
||||
fi
|
||||
|
||||
if bashio::config.true 'silent'; then
|
||||
exec \
|
||||
s6-notifyoncheck -d -n 300 -w 1000 -c "nc -z localhost ${WEBUI_PORT}" \
|
||||
s6-setuidgid abc /usr/bin/qbittorrent-nox --webui-port="${WEBUI_PORT}" > /dev/null
|
||||
else
|
||||
exec \
|
||||
s6-notifyoncheck -d -n 300 -w 1000 -c "nc -z localhost ${WEBUI_PORT}" \
|
||||
s6-setuidgid abc /usr/bin/qbittorrent-nox --webui-port="${WEBUI_PORT}"
|
||||
fi
|
||||
fi
|
||||
|
||||
# --- Launch qBittorrent ---
|
||||
|
||||
# Determine log output based on silent mode
|
||||
QB_OUTPUT="/dev/stdout"
|
||||
if bashio::config.true 'silent'; then
|
||||
QB_OUTPUT="/dev/null"
|
||||
fi
|
||||
|
||||
bashio::log.info "Starting qBittorrent..."
|
||||
|
||||
exec \
|
||||
s6-notifyoncheck -d -n 300 -w 1000 -c "nc -z localhost ${WEBUI_PORT}" \
|
||||
s6-setuidgid abc /usr/bin/qbittorrent-nox --webui-port="${WEBUI_PORT}" > "${QB_OUTPUT}"
|
||||
|
||||
Reference in New Issue
Block a user