diff --git a/qbittorrent/rootfs/etc/services.d/nginx/run b/qbittorrent/rootfs/etc/services.d/nginx/run index 98f93abdb..623775fc4 100644 --- a/qbittorrent/rootfs/etc/services.d/nginx/run +++ b/qbittorrent/rootfs/etc/services.d/nginx/run @@ -1,235 +1,11 @@ #!/usr/bin/with-contenv bashio # shellcheck shell=bash +set -e +# ============================================================================== -set -euo pipefail +# Wait for transmission to become available +bashio::net.wait_for 8080 localhost 900 -export PATH="/usr/local/sbin:/usr/local/bin:${PATH}" +bashio::log.info "Starting NGinx..." -# File where the real (non-VPN) public IP is stored -REAL_IP_FILE="/currentip" - -# How often to re-check VPN IP (seconds) -VPN_CHECK_INTERVAL="${VPN_CHECK_INTERVAL:-300}" - -# Service used to get country for a given IP. -# If it contains "%s", the IP will be formatted into it. -# Otherwise we assume an ipinfo-like base URL and call "${VPN_INFO_URL%/}/${ip}/json". -VPN_INFO_URL="${VPN_INFO_URL:-https://ipinfo.io}" - -# ------------------------------- -# Helpers -# ------------------------------- - -_fetch_public_ip() { - local resp - local url - local urls=( - "https://icanhazip.com" - "https://ifconfig.me/ip" - "https://api64.ipify.org" - "https://checkip.amazonaws.com" - "https://domains.google.com/checkip" - "https://ipinfo.io/ip" - ) - local shuffled_urls - mapfile -t shuffled_urls < <(printf "%s\n" "${urls[@]}" | shuf) - - for url in "${shuffled_urls[@]}"; do - resp=$(curl -fsS --max-time 5 "${url}" 2>/dev/null || true) - resp="${resp//[[:space:]]/}" - - # Validate IPv4 or IPv6 - if [[ "${resp}" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]] || [[ "${resp}" =~ ^[0-9a-fA-F:]+$ ]]; then - printf '%s\n' "${resp}" - return 0 - fi - done - - return 1 -} - -read_real_ip() { - # Reads the "real" IP saved before VPN start - if [[ -f "${REAL_IP_FILE}" ]]; then - local ip - ip="$(tr -d '[:space:]' < "${REAL_IP_FILE}")" - if [[ -n "${ip}" ]]; then - echo "${ip}" - return 0 - fi - fi - # No baseline is not fatal: just return empty - echo "" - return 0 -} - -get_ip_info() { - # Outputs: " " on success - local ip country json info_url url - - if ! ip="$(_fetch_public_ip)"; then - bashio::log.warning "Unable to determine external IP from fallback IP services." - return 1 - fi - - country="Unknown" - info_url="${VPN_INFO_URL:-}" - - if [[ -n "${info_url}" ]]; then - # Build URL used to get country for this IP - if [[ "${info_url}" == *"%s"* ]]; then - # Template style, e.g. "https://ipinfo.io/%s/json" - printf -v url "${info_url}" "${ip}" - else - # ipinfo-style base URL - url="${info_url%/}/${ip}/json" - fi - - if json="$(curl -fsS --max-time 10 "${url}" 2>/dev/null || true)" && [[ -n "${json}" ]]; then - country="$( - printf '%s\n' "${json}" \ - | sed -n 's/.*"country"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' \ - | head -n1 - )" - [[ -z "${country}" ]] && country="Unknown" - fi - fi - - printf '%s %s\n' "${ip}" "${country}" -} - -wait_for_vpn_ip() { - # Waits until: - # - we can read an external IP, AND - # - it is different from REAL_IP (if REAL_IP is known) - # Outputs " " when ready. - local attempt out ip country - - for attempt in {1..5}; do - if out="$(get_ip_info)"; then - read -r ip country <<< "${out}" - - if [[ -n "${REAL_IP:-}" ]] && [[ "${ip}" == "${REAL_IP}" ]]; then - bashio::log.warning \ - "External IP still equals real IP (${ip}); VPN not ready yet (attempt ${attempt}/5)." - else - printf '%s %s\n' "${ip}" "${country}" - return 0 - fi - else - bashio::log.warning "Unable to query external IP (attempt ${attempt}/5)." - fi - - sleep 5 - done - - return 1 -} - -start_nginx_background() { - bashio::log.info "Starting nginx..." - nginx & - echo $! -} - -# ------------------------------- -# Main logic -# ------------------------------- - -# Detect VPN mode(s) -vpn_openvpn=false -vpn_wireguard=false - -if bashio::config.true 'openvpn_enabled'; then - vpn_openvpn=true -fi - -if bashio::config.true 'wireguard_enabled'; then - vpn_wireguard=true -fi - -# If both are enabled, that's a configuration error -if [[ "${vpn_openvpn}" == true && "${vpn_wireguard}" == true ]]; then - bashio::log.error "Both OpenVPN and WireGuard are enabled. Only one VPN mode is supported." - exit 1 -fi - -vpn_enabled=false -vpn_mode="none" - -if [[ "${vpn_openvpn}" == true ]]; then - vpn_enabled=true - vpn_mode="OpenVPN" -fi - -if [[ "${vpn_wireguard}" == true ]]; then - vpn_enabled=true - vpn_mode="WireGuard" -fi - -if [[ "${vpn_enabled}" != true ]]; then - # No VPN: just boot nginx, no IP leak monitoring - bashio::log.info "No VPN enabled (OpenVPN/WireGuard). Starting nginx without IP monitoring." - exec nginx -fi - -# Read the baseline "real" IP from /currentip -REAL_IP="$(read_real_ip)" - -if [[ -n "${REAL_IP}" ]]; then - bashio::log.info "Real (non-VPN) IP from ${REAL_IP_FILE}: ${REAL_IP}" -else - bashio::log.warning "Real IP file ${REAL_IP_FILE} missing or empty; IP leak detection will be less strict." -fi - -bashio::log.info "VPN mode detected: ${vpn_mode}. Enabling IP leak protection and periodic monitoring." - -# Wait until VPN is up and external IP != REAL_IP -if ! VPN_INFO_OUT="$(wait_for_vpn_ip)"; then - bashio::log.error "Unable to obtain a VPN external IP different from real IP. Exiting to avoid leaking traffic." - bashio::addon.stop -fi - -read -r VPN_IP VPN_COUNTRY <<< "${VPN_INFO_OUT}" - -bashio::log.info "VPN external IP: ${VPN_IP} (${VPN_COUNTRY})" - -# Start nginx in background and monitor -nginx_pid="$(start_nginx_background)" - -# Forward termination signals -trap ' - bashio::log.info "Signal received, stopping nginx..." - kill "'"${nginx_pid}"'" 2>/dev/null || true - wait "'"${nginx_pid}"'" 2>/dev/null || true - exit 0 -' SIGTERM SIGINT SIGHUP - -# Monitoring loop -while true; do - # If nginx died, stop this service and let s6 handle restart policy - if ! kill -0 "${nginx_pid}" 2>/dev/null; then - bashio::log.error "nginx process exited unexpectedly; leaving service." - exit 1 - fi - - sleep "${VPN_CHECK_INTERVAL}" - - if ! current_out="$(get_ip_info)"; then - bashio::log.warning "Failed to refresh external IP; keeping previous assumptions." - continue - fi - - read -r current_ip current_country <<< "${current_out}" - - bashio::log.info "Current external IP: ${current_ip} (${current_country})" - - # If we know the real IP, treat equality as a leak - if [[ -n "${REAL_IP}" ]] && [[ "${current_ip}" == "${REAL_IP}" ]]; then - bashio::log.error "IP LEAK DETECTED: current external IP ${current_ip} matches real IP ${REAL_IP}." - bashio::log.error "Stopping nginx and exiting so the supervisor can restart the add-on." - kill "${nginx_pid}" 2>/dev/null || true - wait "${nginx_pid}" 2>/dev/null || true - exit 1 - fi -done +exec nginx