diff --git a/qbittorrent/rootfs/etc/s6-overlay/s6-rc.d/svc-qbittorrent/run b/qbittorrent/rootfs/etc/s6-overlay/s6-rc.d/svc-qbittorrent/run index 7128d30c2..6e00130b4 100644 --- a/qbittorrent/rootfs/etc/s6-overlay/s6-rc.d/svc-qbittorrent/run +++ b/qbittorrent/rootfs/etc/s6-overlay/s6-rc.d/svc-qbittorrent/run @@ -5,11 +5,123 @@ WEBUI_PORT=${WEBUI_PORT:-8080} export PATH="/usr/local/sbin:/usr/local/bin:${PATH}" +# Global variable used by WireGuard bring-up helper +output="" + if bashio::config.true 'silent'; then sed -i 's|/proc/1/fd/1 hassio;|off;|g' /etc/nginx/nginx.conf fi +# +# --- Shared helpers: IP + country with rate-limit fallback, leak monitor --- +# + +_fetch_public_ip() { + # Try several providers; any HTTP error (incl. 429) makes curl -f fail, + # so we fall back to the next one. + local resp + local url + + for url in \ + "https://ifconfig.co/ip" \ + "https://api64.ipify.org" \ + "https://ipecho.net/plain" + do + resp=$(curl -fsS --max-time 10 "${url}" 2>/dev/null || true) + resp="${resp//[[:space:]]/}" + if [[ -n "${resp}" ]]; then + printf '%s\n' "${resp}" + return 0 + fi + done + + return 1 +} + +_fetch_country_code() { + # Same idea for country ISO code. + local resp + local url + + for url in \ + "https://ifconfig.co/country-iso" \ + "https://ipinfo.io/country" + do + resp=$(curl -fsS --max-time 10 "${url}" 2>/dev/null || true) + resp="${resp//[[:space:]]/}" + if [[ -n "${resp}" ]]; then + printf '%s\n' "${resp}" + return 0 + fi + done + + return 1 +} + +_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} + + if ! command -v curl >/dev/null 2>&1; then + bashio::log.warning "${vpn_label}: curl not found; VPN leak monitoring disabled." + return 0 + fi + + if ! bashio::fs.file_exists "${current_ip_file}"; then + bashio::log.warning "${vpn_label}: baseline IP file ${current_ip_file} not found; VPN leak monitoring disabled." + return 0 + fi + + baseline_ip="$(tr -d '[:space:]' < "${current_ip_file}")" + if [[ -z "${baseline_ip}" ]]; then + bashio::log.warning "${vpn_label}: baseline IP in ${current_ip_file} is empty; VPN leak monitoring disabled." + 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." + 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)." + else + if country="$(_fetch_country_code || true)"; then + bashio::log.info "${vpn_label}: public IP: ${vpn_ip} (${country})." + else + bashio::log.info "${vpn_label}: public IP: ${vpn_ip} (country unknown)." + fi + + 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. + s6-svscanctl -t /var/run/s6/services 2>/dev/null || true + exit 1 + fi + fi + + sleep "${interval}" + done +} + +# +# --- Main logic: OpenVPN / WireGuard / qBittorrent --- +# + if bashio::config.true 'openvpn_enabled'; then + # Start leak monitor for OpenVPN in the background + _vpn_monitor_public_ip "OpenVPN" & + exec /usr/sbin/openvpn \ --config /config/openvpn/config.ovpn \ --script-security 2 \ @@ -43,6 +155,7 @@ else 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}" @@ -56,13 +169,11 @@ else _wireguard_up_with_iptables_fallback() { local config_path="$1" - local status + local status=0 - output="" - output=$(wg-quick up "${config_path}" 2>&1) - status=$? + output="$(wg-quick up "${config_path}" 2>&1)" || status=$? - if [ "$status" -eq 0 ]; then + if [ "${status}" -eq 0 ]; then return 0 fi @@ -70,8 +181,7 @@ else 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=$? + 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 @@ -86,9 +196,10 @@ else bashio::log.warning "First attempt output:${bashio::constants.LF}${output}" ipv4_config="${WIREGUARD_STATE_DIR}/${wireguard_interface}-ipv4.conf" - echo -n > "${ipv4_config}" + : > "${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 = }" @@ -137,6 +248,9 @@ else 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