From 47a43c82b453a2b8f16babc51d5831ac84dde78f Mon Sep 17 00:00:00 2001 From: litinoveweedle <15144712+litinoveweedle@users.noreply.github.com> Date: Sun, 25 Jan 2026 09:21:49 +0100 Subject: [PATCH 01/35] initial VPN logic implementation both Wireguard and OpenVPN are now handled by single service file This remove dependency to external tools Simplify vpn routing by using dedicated routing table which is used by qbittorrent torrent client listenning directly on the vpn interface. To prevent DNS leeks traffic to addon configured DNS servers is forced to use same dedicated routing table --- qbittorrent/config.yaml | 4 +- .../s6-overlay/s6-rc.d/svc-qbittorrent/run | 138 +------ .../rootfs/etc/services.d/vpn-monitor/run | 17 +- qbittorrent/rootfs/usr/local/bin/resolvconf | 86 ----- .../rootfs/usr/local/sbin/ip6tables-restore | 75 ---- .../rootfs/usr/local/sbin/iptables-restore | 54 --- qbittorrent/rootfs/usr/local/sbin/sysctl | 17 - qbittorrent/rootfs/usr/local/sbin/vpn | 363 ++++++++++++++++++ 8 files changed, 377 insertions(+), 377 deletions(-) delete mode 100644 qbittorrent/rootfs/usr/local/bin/resolvconf delete mode 100644 qbittorrent/rootfs/usr/local/sbin/ip6tables-restore delete mode 100644 qbittorrent/rootfs/usr/local/sbin/iptables-restore delete mode 100644 qbittorrent/rootfs/usr/local/sbin/sysctl create mode 100644 qbittorrent/rootfs/usr/local/sbin/vpn diff --git a/qbittorrent/config.yaml b/qbittorrent/config.yaml index 432146422..9bbae572b 100644 --- a/qbittorrent/config.yaml +++ b/qbittorrent/config.yaml @@ -130,7 +130,7 @@ schema: localdisks: str? networkdisks: str? openvpn_alt_mode: bool? - openvpn_config: str? + openvpn_config: match(^\w+\.conf$)? openvpn_enabled: bool? openvpn_password: str? openvpn_username: str? @@ -138,7 +138,7 @@ schema: run_duration: str? silent: bool? ssl: bool - wireguard_config: str? + wireguard_config: match(^\w+\.conf$)? wireguard_enabled: bool? whitelist: str? slug: qbittorrent 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 b0f1a99cf..ec0e73b49 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 @@ -10,125 +10,6 @@ if bashio::config.true 'silent'; then sed -i 's|/proc/1/fd/1 hassio;|off;|g' /etc/nginx/nginx.conf fi -# --- 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 2>&1; 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 - - # Allow sysctl failures on read-only hosts while keeping the interface up - if echo "${output}" | grep -qi 'net\.ipv4\.conf\.all\.src_valid_mark=1'; then - if echo "${output}" | grep -qiE 'read-only file system|operation not permitted'; then - if ip link show "${wireguard_interface}" >/dev/null 2>&1; then - bashio::log.warning 'WireGuard applied but sysctl net.ipv4.conf.all.src_valid_mark=1 could not be set (read-only). Continuing.' - status=0 - return 0 - fi - fi - 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 --- openvpn_enabled=false @@ -148,24 +29,9 @@ if [[ "${openvpn_enabled}" == true && "${wireguard_enabled}" == true ]]; then fi if [[ "${openvpn_enabled}" == true ]]; then - - exec /usr/sbin/openvpn \ - --config /config/openvpn/config.ovpn \ - --script-security 2 \ - --up /etc/openvpn/up.sh \ - --down /etc/openvpn/down.sh \ - --pull-filter ignore "route-ipv6" \ - --pull-filter ignore "ifconfig-ipv6" \ - --pull-filter ignore "tun-ipv6" \ - --pull-filter ignore "redirect-gateway ipv6" \ - --pull-filter ignore "dhcp-option DNS6" \ - & - + /usr/local/sbin/vpn openvpn up elif [[ "${wireguard_enabled}" == true ]]; then - - # Run modularized WireGuard setup - _setup_wireguard - + /usr/local/sbin/vpn wireguard up fi # --- Launch qBittorrent --- diff --git a/qbittorrent/rootfs/etc/services.d/vpn-monitor/run b/qbittorrent/rootfs/etc/services.d/vpn-monitor/run index 5117c5fa3..4ed9f4812 100755 --- a/qbittorrent/rootfs/etc/services.d/vpn-monitor/run +++ b/qbittorrent/rootfs/etc/services.d/vpn-monitor/run @@ -129,13 +129,6 @@ if bashio::config.true 'openvpn_enabled'; then vpn_openvpn=true fi -if [[ "${vpn_openvpn}" == true ]] && ! bashio::config.true 'openvpn_alt_mode'; then - VPN_INTERFACE="tun0" - bashio::log.info "VPN monitor set to query external IP through interface ${VPN_INTERFACE} (interface binding)." -else - VPN_INTERFACE="" -fi - if bashio::config.true 'wireguard_enabled'; then vpn_wireguard=true fi @@ -151,6 +144,16 @@ if [[ "${vpn_openvpn}" == true && "${vpn_wireguard}" == true ]]; then exit 1 fi +if [[ "${vpn_openvpn}" == true ]] && ! bashio::config.true 'openvpn_alt_mode'; then + VPN_INTERFACE=$(cat "/var/run/openvpn/interface") + bashio::log.info "VPN monitor set to query external IP through interface ${VPN_INTERFACE} (interface binding)." +elif [[ "${vpn_wireguard}" == true ]]; then + VPN_INTERFACE=$(cat "/var/run/wireguard/interface") + bashio::log.info "VPN monitor set to query external IP through interface ${VPN_INTERFACE} (interface binding)." +else + VPN_INTERFACE="" +fi + REAL_IP="$(read_real_ip)" if [[ -n "${REAL_IP}" ]]; then diff --git a/qbittorrent/rootfs/usr/local/bin/resolvconf b/qbittorrent/rootfs/usr/local/bin/resolvconf deleted file mode 100644 index 3e2a91880..000000000 --- a/qbittorrent/rootfs/usr/local/bin/resolvconf +++ /dev/null @@ -1,86 +0,0 @@ -#!/bin/sh -set -eu - -STATE_DIR="/var/run/wireguard/resolvconf" -BACKUP_FILE="${STATE_DIR}/resolv.conf.backup" - -mkdir -p "${STATE_DIR}" - -if [ "$#" -eq 0 ]; then - exit 0 -fi - -command="$1" -shift || true - -restore_backup() { - if [ -f "${BACKUP_FILE}" ]; then - cat "${BACKUP_FILE}" > /etc/resolv.conf - fi -} - -apply_dns() { - iface="$1" - shift || true - - # Skip optional arguments such as -m or -x - while [ "$#" -gt 0 ]; do - case "$1" in - -m|-p|-w) - shift 2 || true - ;; - -x|-y|-Z) - shift 1 || true - ;; - --) - shift - break - ;; - *) - break - ;; - esac - done - - tmp_file="${STATE_DIR}/${iface}.conf" - cat > "${tmp_file}" - - if [ ! -f "${BACKUP_FILE}" ]; then - cp /etc/resolv.conf "${BACKUP_FILE}" 2>/dev/null || true - fi - - { - echo "# Generated by WireGuard add-on resolvconf shim" - cat "${tmp_file}" - } > /etc/resolv.conf -} - -case "${command}" in - -a) - if [ "$#" -eq 0 ]; then - exit 0 - fi - apply_dns "$@" - ;; - -d) - if [ "$#" -gt 0 ]; then - rm -f "${STATE_DIR}/$1.conf" - fi - restore_backup - ;; - -u) - latest_conf="$(find "${STATE_DIR}" -maxdepth 1 -type f -name '*.conf' -print | head -n 1 || true)" - if [ -n "${latest_conf}" ] && [ -f "${latest_conf}" ]; then - { - echo "# Generated by WireGuard add-on resolvconf shim" - cat "${latest_conf}" - } > /etc/resolv.conf - else - restore_backup - fi - ;; - *) - # Treat other commands as successful no-ops to remain compatible with wg-quick. - exit 0 - ;; -esac diff --git a/qbittorrent/rootfs/usr/local/sbin/ip6tables-restore b/qbittorrent/rootfs/usr/local/sbin/ip6tables-restore deleted file mode 100644 index 23bff73d3..000000000 --- a/qbittorrent/rootfs/usr/local/sbin/ip6tables-restore +++ /dev/null @@ -1,75 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -REAL_IP6TABLES_RESTORE="/sbin/ip6tables-restore" -if [[ ! -x "${REAL_IP6TABLES_RESTORE}" ]]; then - REAL_IP6TABLES_RESTORE="/usr/sbin/ip6tables-restore" -fi - -cleanup() { - local exit_code=$? - [[ -n "${RULES_FILE:-}" && -f "${RULES_FILE}" ]] && rm -f "${RULES_FILE}" - [[ -n "${SANITIZED_FILE:-}" && -f "${SANITIZED_FILE}" ]] && rm -f "${SANITIZED_FILE}" - return $exit_code -} -trap cleanup EXIT - -RULES_FILE="$(mktemp)" -cat > "${RULES_FILE}" - -ipv6_unavailable() { - local message="$1" - [[ $message =~ [Tt]able[[:space:]]does[[:space:]]not[[:space:]]exist ]] && return 0 - [[ $message =~ address[[:space:]]family[[:space:]]not[[:space:]]supported ]] && return 0 - [[ $message =~ can[[:punct:]]t[[:space:]]initialize[[:space:]]ip6tables[[:space:]]table ]] && return 0 - [[ $message =~ IPv6[[:space:]]support[[:space:]]not[[:space:]]available ]] && return 0 - return 1 -} - -# First attempt with the original ruleset -output="" -if output="$(${REAL_IP6TABLES_RESTORE} "$@" < "${RULES_FILE}" 2>&1)"; then - [[ -n "${output}" ]] && printf '%s\n' "${output}" >&2 - exit 0 -fi -status=$? - -# Retry without comment matches if the kernel is missing the comment module -SANITIZED_FILE="$(mktemp)" -sed -E 's/-m[[:space:]]+comment[[:space:]]+--comment[[:space:]]+"[^"]*"//g' "${RULES_FILE}" > "${SANITIZED_FILE}" - -retry_output="" -if retry_output="$(${REAL_IP6TABLES_RESTORE} "$@" < "${SANITIZED_FILE}" 2>&1)"; then - printf '%s\n' "ip6tables-restore failed with comment matches; reapplied without comments." >&2 - printf '%s\n' "Original error: ${output}" >&2 - [[ -n "${retry_output}" ]] && printf '%s\n' "${retry_output}" >&2 - exit 0 -fi -retry_status=$? - -# Final fallback: try legacy backend if available -legacy_output="" -for legacy in /sbin/ip6tables-restore-legacy /usr/sbin/ip6tables-restore-legacy; do - if [[ -x "${legacy}" ]]; then - if legacy_output="$(${legacy} "$@" < "${RULES_FILE}" 2>&1)"; then - printf '%s\n' "ip6tables-restore failed; succeeded using legacy backend." >&2 - printf '%s\n' "Original error: ${output}" >&2 - [[ -n "${legacy_output}" ]] && printf '%s\n' "${legacy_output}" >&2 - exit 0 - fi - fi - -done - -if ipv6_unavailable "${output}" || ipv6_unavailable "${retry_output}" || ipv6_unavailable "${legacy_output}"; then - printf '%s\n' "IPv6 firewall support not detected; skipping IPv6 ruleset restore and continuing." >&2 - printf '%s\n' "Original error: ${output}" >&2 - [[ -n "${retry_output}" ]] && printf '%s\n' "Sanitized retry error: ${retry_output}" >&2 - [[ -n "${legacy_output}" ]] && printf '%s\n' "Legacy backend error: ${legacy_output}" >&2 - exit 0 -fi - -printf '%s\n' "ip6tables-restore failed and fallbacks were unsuccessful." >&2 -printf '%s\n' "Original error: ${output}" >&2 -printf '%s\n' "Sanitized retry error: ${retry_output}" >&2 -exit ${retry_status} diff --git a/qbittorrent/rootfs/usr/local/sbin/iptables-restore b/qbittorrent/rootfs/usr/local/sbin/iptables-restore deleted file mode 100644 index 2219b563c..000000000 --- a/qbittorrent/rootfs/usr/local/sbin/iptables-restore +++ /dev/null @@ -1,54 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -REAL_IPTABLES_RESTORE="/sbin/iptables-restore" -if [[ ! -x "${REAL_IPTABLES_RESTORE}" ]]; then - REAL_IPTABLES_RESTORE="/usr/sbin/iptables-restore" -fi - -cleanup() { - local exit_code=$? - [[ -n "${RULES_FILE:-}" && -f "${RULES_FILE}" ]] && rm -f "${RULES_FILE}" - [[ -n "${SANITIZED_FILE:-}" && -f "${SANITIZED_FILE}" ]] && rm -f "${SANITIZED_FILE}" - return $exit_code -} -trap cleanup EXIT - -RULES_FILE="$(mktemp)" -cat > "${RULES_FILE}" - -# First attempt with the original ruleset -if output="$(${REAL_IPTABLES_RESTORE} "$@" < "${RULES_FILE}" 2>&1)"; then - [[ -n "${output}" ]] && printf '%s\n' "${output}" >&2 - exit 0 -fi -status=$? - -# Retry without comment matches if the kernel is missing the comment module -SANITIZED_FILE="$(mktemp)" -sed -E 's/-m[[:space:]]+comment[[:space:]]+--comment[[:space:]]+"[^"]*"//g' "${RULES_FILE}" > "${SANITIZED_FILE}" - -if retry_output="$(${REAL_IPTABLES_RESTORE} "$@" < "${SANITIZED_FILE}" 2>&1)"; then - printf '%s\n' "iptables-restore failed with comment matches; reapplied without comments." >&2 - printf '%s\n' "Original error: ${output}" >&2 - [[ -n "${retry_output}" ]] && printf '%s\n' "${retry_output}" >&2 - exit 0 -fi -retry_status=$? - -# Final fallback: try legacy backend if available -for legacy in /sbin/iptables-restore-legacy /usr/sbin/iptables-restore-legacy; do - if [[ -x "${legacy}" ]]; then - if legacy_output="$(${legacy} "$@" < "${RULES_FILE}" 2>&1)"; then - printf '%s\n' "iptables-restore failed; succeeded using legacy backend." >&2 - printf '%s\n' "Original error: ${output}" >&2 - [[ -n "${legacy_output}" ]] && printf '%s\n' "${legacy_output}" >&2 - exit 0 - fi - fi -done - -printf '%s\n' "iptables-restore failed and fallbacks were unsuccessful." >&2 -printf '%s\n' "Original error: ${output}" >&2 -printf '%s\n' "Sanitized retry error: ${retry_output}" >&2 -exit ${retry_status} diff --git a/qbittorrent/rootfs/usr/local/sbin/sysctl b/qbittorrent/rootfs/usr/local/sbin/sysctl deleted file mode 100644 index b76c18c88..000000000 --- a/qbittorrent/rootfs/usr/local/sbin/sysctl +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -REAL_SYSCTL="/sbin/sysctl" -if [[ ! -x "${REAL_SYSCTL}" ]]; then - REAL_SYSCTL="/usr/sbin/sysctl" -fi - -if [[ "$#" -ge 2 && "$1" == "-q" && "$2" == "net.ipv4.conf.all.src_valid_mark=1" ]]; then - if "${REAL_SYSCTL}" "$@" >/dev/null 2>&1; then - exit 0 - fi - # Suppress failure for this specific key to keep wg-quick from aborting in unprivileged environments. - exit 0 -fi - -exec "${REAL_SYSCTL}" "$@" diff --git a/qbittorrent/rootfs/usr/local/sbin/vpn b/qbittorrent/rootfs/usr/local/sbin/vpn new file mode 100644 index 000000000..b3ac0f7fd --- /dev/null +++ b/qbittorrent/rootfs/usr/local/sbin/vpn @@ -0,0 +1,363 @@ +#!/usr/bin/with-contenv bashio +# shellcheck shell=bas + +# --- WireGuard Specific Logic --- + +declare -A config +config["MySelf"]="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")" +IFS=',' read -ra dns_servers <<< $(bashio::config 'DNS_server' | tr -d ' ') +config["DnsServers"]="${dns_servers[*]}" + +_parse_config() { + local -n config_ref="$1" + local config_file="$2" + + while IFS= read -r line || [[ -n "$line" ]]; do + # Skip comments and empty lines + [[ "$line" =~ ^[#!] ]] && continue + # Extract key and value using regex (trim spaces) + #if [[ "$line" =~ ^[[:space:]]*([^ =]+)[[:space:]]*=[[:space:]]*(.*)[[:space:]]* ]]; then + if [[ "$line" =~ ^[[:space:]]*([^=[:space:]]+)[=[:space:]]+(.*)[[:space:]]* ]]; then + key="${BASH_REMATCH[1]}" + value="${BASH_REMATCH[2]}" + config_ref["$key"]="$value" + fi + done < "$config_file" +} + +_cmd() { + cmd="$1" + bashio::log.info "Executing command: ${cmd}" + eval "${cmd}" +} + +_is_ip_address() { + if [ "$1" != "${1#*[0-9].[0-9]}" ]; then + return 1 # IPv4 + elif [ "$1" != "${1#*:[0-9a-fA-F]}" ]; then + return 2 # IPv6 + else + return 0 # Not an IP address + fi +} + +_resolvconf() { + local mode=$1 + local resolv_conf="/etc/resolv.conf" + local resolv_backup="/etc/resolv.conf.bak" + + if [ "${mode}" = "reset" ]; then + bashio::log.info "Resetting ${resolv_conf} to default DNS servers." + if bashio::fs.file_exists "${resolv_backup}"; then + cp "${resolv_backup}" "${resolv_conf}" + else + bashio::log.warning "No original resolv.conf backup found. Leaving as is." + fi + elif [ "${mode}" = "update" ]; then + bashio::log.info "Updating ${resolv_conf} with DNS servers: ${config["DnsServers"]}" + if ! bashio::fs.file_exists "${resolv_backup}"; then + cp "${resolv_conf}" "${resolv_backup}" 2>/dev/null || true + fi + { + local dns_ip + echo "# Generated by addon VPN script" + for dns_ip in ${config["DnsServers"]}; do + _is_ip_address "${dns_ip}" + local is_ip=$? + if [ "${is_ip}" -eq 0 ]; then + bashio::log.warning "Ignoring invalid DNS server address: ${dns_ip}" + continue + else + echo "nameserver ${dns_ip}" + fi + done + } > "${resolv_conf}" + fi +} + +_resolve_hostname() { + local hostname=$1 + local ips="" + + # Resolve hostname to IPv6 + mapfile -t ipv6_candidates < <(getent ahostsv6 "${hostname}" | awk '{print $1}' | uniq) + + # Resolve hostname to IPv4 + mapfile -t ipv4_candidates < <(getent ahostsv4 "${hostname}" | awk '{print $1}' | uniq) + + if [ ${#ipv6_candidates[@]} -gt 0 ]; then + bashio::log.debug "Resolved ${hostname} to ${ipv6_candidates[@]}" + ips=${ipv6_candidates[@]} + fi + + if [ ${#ipv4_candidates[@]} -gt 0 ]; then + bashio::log.debug "Resolved ${hostname} to ${ipv4_candidates[@]}" + ips="${ips} ${ipv4_candidates[@]}" + fi + + return $ips +} + +_routing_add() { + local local_ipv4=$(ip addr show ${config["Interface"]} | grep 'inet ' | awk '{print $2}' | cut -d'/' -f1) + local local_ipv6=$(ip addr show ${config["Interface"]} | grep 'inet6 ' | awk '{print $2}' | cut -d'/' -f1) + for ipv4 in ${local_ipv4}; do + config["IPv4Enabled"]="true" + _cmd "ip -4 route add default dev ${config["Interface"]} table ${config["Table"]}" || return 1 + _cmd "ip -4 rule add priority 1 from ${local_ip} table ${config["Table"]}" || return 1 + done + for ipv6 in ${local_ipv6}; do + config["IPv6Enabled"]="true" + _cmd "ip -6 route add default dev ${config["Interface"]} table ${config["Table"]}" || return 1 + _cmd "ip -6 rule add priority 1 from ${local_ip} table ${config["Table"]}" || return 1 + done + + local dns_ip + for dns_ip in ${config["DnsServers"]}; do + _is_ip_address "${dns_ip}" + local is_ip=$? + if [ "${is_ip}" -eq 0 ]; then + bashio::log.warning "Ignoring invalid DNS server address: ${dns_ip}" + continue + elif [ "${is_ip}" -eq 1 ] && [ ${config["IPv4Enabled"]} = "true" ]; then + #_cmd "ip -4 route add ${dns_ip} dev ${config["Interface"]}" || return 1 + _cmd "ip -4 rule add priority 1 to ${dns_ip} table ${config["Table"]}" || return 1 + elif [ "${is_ip}" -eq 2 ] && [ "${config["IPv6Enabled"]}" = "true" ]; then + #_cmd "ip -6 route add ${dns_ip} dev ${config["Interface"]}" || return 1 + _cmd "ip -6 rule add priority 1 to ${dns_ip} table ${config["Table"]}" || return 1 + else + bashio::log.warning "Failed to add route for DNS server: ${dns_ip}" + fi + done + _resolvconf "update" +} + +_routing_del() { + _resolvconf "reset" + while _cmd "ip -4 rule del priority 1 from all table ${config["Table"]} 2>/dev/null"; do :; done + while _cmd "ip -4 rule del priority 1 to all table ${config["Table"]} 2>/dev/null"; do :; done + while _cmd "ip -4 route del default dev ${config["Interface"]} table ${config["Table"]} 2>/dev/null"; do :; done + while _cmd "ip -6 rule del priority 1 from all table ${config["Table"]} 2>/dev/null"; do :; done + while _cmd "ip -6 rule del priority 1 to all table ${config["Table"]} 2>/dev/null"; do :; done + while _cmd "ip -6 route del default dev ${config["Interface"]} table ${config["Table"]} 2>/dev/null"; do :; done +} + +_wireguard_up() { + bashio::log.warn "Bringing up Wireguard interface on ${config["Interface"]}..." + bashio::log.warn "Using Wireguard configuration file: ${config["ConfigFile"]}" + bashio::log.warn "This script force Wireguard to ignore any routes and DNS settings." + bashio::log.warn "Default route will be inserted into custom routing table: ${config["Table"]}" + bashio::log.warn "This routing table will be used for traffic from the VPN interface and to configured DNS servers." + bashio::log.warn "Qbittorrent bittorrent client shall be set to use the VPN interface ${config["Interface"]} only." + + _cmd "ip link add dev ${config["Interface"]} type wireguard" || return 1 + local allowed_ips="" + for local_ip in ${config["Address"]}; do + _is_ip_address "${local_ip}" + local is_ip=$? + if [ "${is_ip}" -eq 1 ]; then + allowed_ips="${allowed_ips},0.0.0.0/0" + _cmd "ip addr add ${local_ip} dev ${config["Interface"]}" || return 1 + elif [ "${is_ip}" -eq 2 ]; then + allowed_ips="${allowed_ips},::/0" + _cmd "ip addr add ${local_ip} dev ${config["Interface"]}" || return 1 + else + bashio::log.warning "Ignoring invalid local IP address: ${local_ip}" + fi + done + allowed_ips="${allowed_ips#,}" + if [ -z "${allowed_ips}" ]; then + bashio::log.error "No valid local IP addresses configured." + return 1 + fi + _cmd "wg set ${config["Interface"]} listen-port ${config["ListenPort"]} private-key ${config["PrivateKey"]}" || return 1 + _cmd "wg set ${config["Interface"]} peer ${config["PublicKey"]} endpoint ${config["EndpointIP"]}:${config["EndpointPort"]} allowed-ips ${allowed_ips}" || return 1 + if [ -n "${config["PersistentKeepalive"]}" ]; then + _cmd "wg set ${config["Interface"]} peer ${config["PublicKey"]} persistent-keepalive ${config["PersistentKeepalive"]}" || return 1 + fi + _cmd "ip link set ${config["Interface"]} up" || return 1 + _routing_add +} + +_wireguard_down() { + _routing_del + _cmd "ip link set ${config["Interface"]} down" 2>/dev/null || true + _cmd "ip link del dev ${config["Interface"]}" 2>/dev/null || true +} + +wireguard() { + local mode=$1 + local interface + local config_file + local WIREGUARD_STATE_DIR="/var/run/wireguard" + + if ! bashio::fs.file_exists "${WIREGUARD_STATE_DIR}/interface"; then + bashio::exit.nok 'WireGuard runtime configuration not prepared. Please restart the add-on.' + fi + interface=$(cat "${WIREGUARD_STATE_DIR}/interface") + if [ -z "${interface}" ]; then + bashio::exit.nok 'WireGuard runtime configuration not prepared. Please restart the add-on.' + fi + if ! bashio::fs.file_exists "${WIREGUARD_STATE_DIR}/config"; then + bashio::exit.nok 'WireGuard runtime configuration not prepared. Please restart the add-on.' + fi + config_file=$(cat "${WIREGUARD_STATE_DIR}/config") + if [ -z "${config_file}" ]; then + bashio::exit.nok 'WireGuard runtime configuration not prepared. Please restart the add-on.' + fi + + _parse_config config "${config_file}" + config["Interface"]="${interface}" + config["ConfigFile"]="${config_file}" + config["Table"]="${config["Table"]:-1000}" + config["ListenPort"]="${config["ListenPort"]:-51820}" + config["EndpointHost"]="${config["Endpoint"]%:*}" + config["EndpointPort"]="${config["Endpoint"]##*:}" + config["IPv4Enabled"]="false" + config["IPv6Enabled"]="false" + for key in "${!config[@]}"; do + bashio::log.debug "${key}: ${config[$key]}" + done + + echo ${config["PrivateKey"]} > ${WIREGUARD_STATE_DIR}/privatekey + config["PrivateKey"]="${WIREGUARD_STATE_DIR}/privatekey" + + if [ "${mode}" = "up" ]; then + bashio::log.info "Starting WireGuard interface ${config["Interface"]}..." + if _is_ip_address ${config["EndpointHost"]}; then + local endpoint_ips=$(_resolve_hostname ${config["EndpointHost"]}) + if [ ${#endpoint_ips[@]} -eq 0 ]; then + bashio::log.error "Failed to resolve WireGuard endpoint hostname: ${config["EndpointHost"]}" + bashio::exit.nok 'WireGuard start failed.' + fi + for endpoint_ip in "${endpoint_ips[@]}"; do + bashio::log.info "Resolved WireGuard endpoint hostname ${config["EndpointHost"]} to IP: ${endpoint_ip}" + config["EndpointIP"]="${endpoint_ip}" + if _wireguard_up; then + bashio::log.info "WireGuard interface ${config["Interface"]} is up." + bashio::exit.ok 'WireGuard started.' + fi + bashio::log.error 'WireGuard failed to establish connection.' + _wireguard_down + done + else + bashio::log.info "WireGuard endpoint ${config["EndpointHost"]} is a valid IP address. Using as is." + config["EndpointIP"]="${config["EndpointHost"]}" + if _wireguard_up; then + bashio::log.info "WireGuard interface ${config["Interface"]} is up." + bashio::exit.ok 'WireGuard started.' + fi + bashio::log.error 'WireGuard failed to establish connection.' + _wireguard_down + fi + elif [ "${mode}" = "down" ]; then + bashio::log.info "Stopping WireGuard interface ${config["Interface"]}..." + _wireguard_down + bashio::log.info "WireGuard interface ${config["Interface"]} is down." + bashio::exit.ok 'WireGuard stopped.' + else + bashio::log.error "Invalid WireGuard mode specified. Use 'up' or 'down'." + bashio::exit.nok 'WireGuard start failed.' + fi + + bashio::exit.nok 'WireGuard start failed.' +} + +_openvpn_up() { + bashio::log.warn "Bringing up OpenVPN interface on ${config["Interface"]}..." + bashio::log.warn "Using OpenVPN configuration file: ${config["ConfigFile"]}" + bashio::log.warn "This script force OpenvPN to ignore any routes and DNS settings pushed by the server." + bashio::log.warn "Default route will be inserted into custom routing table: ${config["Table"]}" + bashio::log.warn "This routing table will be used for traffic from the VPN interface and to configured DNS servers." + bashio::log.warn "Qbittorrent bittorrent client shall be set to use the VPN interface ${config["Interface"]} only." + + # Register this script as OpenVPN up/down handlers to manage routing + echo "${config["MySelf"]} openvpn postup" > ${config["PostUpScript"]} + chmod 755 ${config["PostUpScript"]} + echo "${config["MySelf"]} openvpn postdown" > ${config["PostDownScript"]} + chmod 755 ${config["PostDownScript"]} + + # Start OpenVPN in the background + # (maybe use setsid instead of nohup to detach completely?) + nohup /usr/sbin/openvpn \ + --config "${config["ConfigFile"]}" \ + --script-security 2 \ + --up ${config["PostUpScript"]} \ + --down ${config["PostDownScript"]} \ + --route-nopull \ + --pull-filter ignore "route" \ + --pull-filter ignore "redirect-gateway" \ + --pull-filter ignore "dhcp-option DNS" \ + --pull-filter ignore "route-ipv6" \ + --pull-filter ignore "redirect-gateway ipv6" \ + --pull-filter ignore "dhcp-option DNS6" \ + & +} + +_openvpn_down() { + # Terminate OpenVPN process + pkill -f "openvpn --config ${config["ConfigFile"]}" || true + _routing_del +} + +openvpn() { + local mode=$1 + local interface + local config_file + local OPENVPN_STATE_DIR="/var/run/openvpn" + + if ! bashio::fs.file_exists "${OPENVPN_STATE_DIR}/interface"; then + bashio::exit.nok 'OpenVPN runtime configuration not prepared. Please restart the add-on.' + fi + interface=$(cat "${OPENVPN_STATE_DIR}/interface") + if [ -z "${interface}" ]; then + bashio::exit.nok 'OpenVPN runtime configuration not prepared. Please restart the add-on.' + fi + if ! bashio::fs.file_exists "${OPENVPN_STATE_DIR}/config"; then + bashio::exit.nok 'OpenVPN runtime configuration not prepared. Please restart the add-on.' + fi + config_file=$(cat "${OPENVPN_STATE_DIR}/config") + if [ -z "${config_file}" ]; then + bashio::exit.nok 'OpenVPN runtime configuration not prepared. Please restart the add-on.' + fi + + _parse_config config "${config_file}" + config["Interface"]="${interface}" + config["ConfigFile"]="${config_file}" + config["Table"]="${config["Table"]:-1000}" + config["PostUpScript"]="${OPENVPN_STATE_DIR}/up.sh" + config["PostDownScript"]="${OPENVPN_STATE_DIR}/down.sh" + + if [ "${mode}" = "up" ]; then + # register up and down scripts + bashio::log.info "Starting OpenVPN with configuration file: ${config_file}..." + _openvpn_up + bashio::exit.ok 'OpenVPN started.' + elif [ "${mode}" = "down" ]; then + bashio::log.info "Stopping OpenVPN..." + _openvpn_down + bashio::exit.ok 'OpenVPN stopped.' + elif [ "${mode}" = "postup" ]; then + _routing_add + elif [ "${mode}" = "postdown" ]; then + _routing_del + else + bashio::log.error "Invalid OpenVPN mode specified. Use 'up', 'down', 'postup', or 'postdown'." + bashio::exit.nok 'OpenVPN start failed.' + fi + bashio::log.info "Starting OpenVPN with configuration file: ${config_file}" +} + + +if [ $# -ne 2 ]; then + bashio::log.error "Invalid number of arguments. Usage: vpn.sh " + bashio::exit.nok 'VPN start failed.' +fi +if [[ "$1" == "wireguard" ]]; then + wireguard "$2" +elif [[ "$1" == "openvpn" ]]; then + openvpn "$2" +else + bashio::log.error "Invalid VPN type specified. Use 'wireguard' or 'openvpn'." + bashio::exit.nok 'VPN start failed.' +fi From 1a0df6c5c271c7919a37240d54378a0e043ab656 Mon Sep 17 00:00:00 2001 From: litinoveweedle <15144712+litinoveweedle@users.noreply.github.com> Date: Sun, 25 Jan 2026 17:23:59 +0100 Subject: [PATCH 02/35] improve dns servers handling fixed routing rule addresses --- qbittorrent/rootfs/usr/local/sbin/vpn | 50 +++++++++++++++++++++------ 1 file changed, 40 insertions(+), 10 deletions(-) diff --git a/qbittorrent/rootfs/usr/local/sbin/vpn b/qbittorrent/rootfs/usr/local/sbin/vpn index b3ac0f7fd..a4ff5dc84 100644 --- a/qbittorrent/rootfs/usr/local/sbin/vpn +++ b/qbittorrent/rootfs/usr/local/sbin/vpn @@ -5,12 +5,11 @@ declare -A config config["MySelf"]="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")" -IFS=',' read -ra dns_servers <<< $(bashio::config 'DNS_server' | tr -d ' ') -config["DnsServers"]="${dns_servers[*]}" _parse_config() { local -n config_ref="$1" local config_file="$2" + local line while IFS= read -r line || [[ -n "$line" ]]; do # Skip comments and empty lines @@ -18,13 +17,32 @@ _parse_config() { # Extract key and value using regex (trim spaces) #if [[ "$line" =~ ^[[:space:]]*([^ =]+)[[:space:]]*=[[:space:]]*(.*)[[:space:]]* ]]; then if [[ "$line" =~ ^[[:space:]]*([^=[:space:]]+)[=[:space:]]+(.*)[[:space:]]* ]]; then - key="${BASH_REMATCH[1]}" - value="${BASH_REMATCH[2]}" + local key="${BASH_REMATCH[1]}" + local value="${BASH_REMATCH[2]}" config_ref["$key"]="$value" fi done < "$config_file" } +_parse_dns() { + local -a dns_servers=() + local dns_ip + + while IFS=',' read -r dns_ip; do + if _is_ip_address "${dns_ip}"; then + bashio::log.warning "Ignoring invalid DNS server address: ${dns_ip}" + continue + fi + dns_servers+=("${dns_ip}") + done <<< $(bashio::config 'DNS_server' | tr -d ' ') + + if [ ${#dns_servers[@]} -eq 0 ]; then + bashio::log.warning "No valid DNS servers configured. Using addon defaults." + dns_servers=("8.8.8.8" "1.1.1.1") + fi + config["DnsServers"]="${dns_servers[*]}" +} + _cmd() { cmd="$1" bashio::log.info "Executing command: ${cmd}" @@ -58,20 +76,30 @@ _resolvconf() { if ! bashio::fs.file_exists "${resolv_backup}"; then cp "${resolv_conf}" "${resolv_backup}" 2>/dev/null || true fi + bashio::log.warn "Overriding ${resolv_conf} with DNS servers: ${config["DnsServers"]}" + local valid_dns="false" { local dns_ip - echo "# Generated by addon VPN script" for dns_ip in ${config["DnsServers"]}; do _is_ip_address "${dns_ip}" local is_ip=$? - if [ "${is_ip}" -eq 0 ]; then + if [ "${is_ip}" -eq 1 ] && [ ${config["IPv4Enabled"]} = "true" ]; then + echo "nameserver ${dns_ip}" + valid_dns="true" + elif [ "${is_ip}" -eq 2 ] && [ "${config["IPv6Enabled"]}" = "true" ]; then + echo "nameserver ${dns_ip}" + valid_dns="true" + else bashio::log.warning "Ignoring invalid DNS server address: ${dns_ip}" continue - else - echo "nameserver ${dns_ip}" fi done } > "${resolv_conf}" + if [ "${valid_dns}" = "false" ]; then + bashio::exit.nok "No valid DNS servers could be written to ${resolv_conf}." + fi + else + bashio::exit.nok "Invalid resolvconf mode specified. Use 'update' or 'reset'." fi } @@ -101,15 +129,16 @@ _resolve_hostname() { _routing_add() { local local_ipv4=$(ip addr show ${config["Interface"]} | grep 'inet ' | awk '{print $2}' | cut -d'/' -f1) local local_ipv6=$(ip addr show ${config["Interface"]} | grep 'inet6 ' | awk '{print $2}' | cut -d'/' -f1) + local ipv4, ipv6 for ipv4 in ${local_ipv4}; do config["IPv4Enabled"]="true" _cmd "ip -4 route add default dev ${config["Interface"]} table ${config["Table"]}" || return 1 - _cmd "ip -4 rule add priority 1 from ${local_ip} table ${config["Table"]}" || return 1 + _cmd "ip -4 rule add priority 1 from ${ipv4} table ${config["Table"]}" || return 1 done for ipv6 in ${local_ipv6}; do config["IPv6Enabled"]="true" _cmd "ip -6 route add default dev ${config["Interface"]} table ${config["Table"]}" || return 1 - _cmd "ip -6 rule add priority 1 from ${local_ip} table ${config["Table"]}" || return 1 + _cmd "ip -6 rule add priority 1 from ${ipv6} table ${config["Table"]}" || return 1 done local dns_ip @@ -353,6 +382,7 @@ if [ $# -ne 2 ]; then bashio::log.error "Invalid number of arguments. Usage: vpn.sh " bashio::exit.nok 'VPN start failed.' fi +_parse_dns if [[ "$1" == "wireguard" ]]; then wireguard "$2" elif [[ "$1" == "openvpn" ]]; then From 7e0281bc21f4e8c1b45c91632e76f377bb9cd6d9 Mon Sep 17 00:00:00 2001 From: litinoveweedle <15144712+litinoveweedle@users.noreply.github.com> Date: Sun, 25 Jan 2026 17:40:17 +0100 Subject: [PATCH 03/35] remove unnecessary OpeVPN scripts --- .../rootfs/etc/cont-init.d/93-openvpn.sh | 6 -- qbittorrent/rootfs/etc/openvpn/.gitkeep | 0 qbittorrent/rootfs/etc/openvpn/down.sh | 36 ------- .../rootfs/etc/openvpn/up-qbittorrent.sh | 9 -- qbittorrent/rootfs/etc/openvpn/up.sh | 97 ------------------- 5 files changed, 148 deletions(-) create mode 100644 qbittorrent/rootfs/etc/openvpn/.gitkeep delete mode 100755 qbittorrent/rootfs/etc/openvpn/down.sh delete mode 100755 qbittorrent/rootfs/etc/openvpn/up-qbittorrent.sh delete mode 100755 qbittorrent/rootfs/etc/openvpn/up.sh diff --git a/qbittorrent/rootfs/etc/cont-init.d/93-openvpn.sh b/qbittorrent/rootfs/etc/cont-init.d/93-openvpn.sh index d1dff3c77..0bab9a487 100755 --- a/qbittorrent/rootfs/etc/cont-init.d/93-openvpn.sh +++ b/qbittorrent/rootfs/etc/cont-init.d/93-openvpn.sh @@ -184,12 +184,6 @@ if bashio::config.true 'openvpn_enabled'; then chmod 755 /config/openvpn/* chmod 755 /etc/openvpn/* chmod 600 /etc/openvpn/credentials - chmod 755 /etc/openvpn/up.sh - chmod 755 /etc/openvpn/down.sh - chmod 755 /etc/openvpn/up-qbittorrent.sh - chmod +x /etc/openvpn/up.sh - chmod +x /etc/openvpn/down.sh - chmod +x /etc/openvpn/up-qbittorrent.sh echo "... openvpn correctly set, qbittorrent will run tunnelled through openvpn" diff --git a/qbittorrent/rootfs/etc/openvpn/.gitkeep b/qbittorrent/rootfs/etc/openvpn/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/qbittorrent/rootfs/etc/openvpn/down.sh b/qbittorrent/rootfs/etc/openvpn/down.sh deleted file mode 100755 index 57d18e39b..000000000 --- a/qbittorrent/rootfs/etc/openvpn/down.sh +++ /dev/null @@ -1,36 +0,0 @@ -#!/bin/sh -# shellcheck disable=SC2154,SC2004,SC2059,SC2086 -# Copyright (c) 2006-2007 Gentoo Foundation -# Distributed under the terms of the GNU General Public License v2 -# Contributed by Roy Marples (uberlord@gentoo.org) - -# If we have a service specific script, run this now -if [ -x /etc/openvpn/"${RC_SVCNAME}"-down.sh ]; then - /etc/openvpn/"${RC_SVCNAME}"-down.sh "$@" -fi - -# Restore resolv.conf to how it was -if [ "${PEER_DNS}" != "no" ]; then - if [ -x /sbin/resolvconf ]; then - /sbin/resolvconf -d "${dev}" - elif [ -e /etc/resolv.conf-"${dev}".sv ]; then - # Important that we cat instead of move incase resolv.conf is - # a symlink and not an actual file - cat /etc/resolv.conf-"${dev}".sv > /etc/resolv.conf - rm -f /etc/resolv.conf-"${dev}".sv - fi -fi - -if [ -n "${RC_SVCNAME}" ]; then - # Re-enter the init script to start any dependant services - if /etc/init.d/"${RC_SVCNAME}" --quiet status; then - export IN_BACKGROUND=true - if [ -d /var/run/s6/container_environment ]; then printf "%s" "true" > /var/run/s6/container_environment/IN_BACKGROUND; fi - printf "%s\n" "IN_BACKGROUND=\"true\"" >> ~/.bashrc - /etc/init.d/"${RC_SVCNAME}" --quiet stop - fi -fi - -exit 0 - -# vim: ts=4 : diff --git a/qbittorrent/rootfs/etc/openvpn/up-qbittorrent.sh b/qbittorrent/rootfs/etc/openvpn/up-qbittorrent.sh deleted file mode 100755 index 0f0101dbc..000000000 --- a/qbittorrent/rootfs/etc/openvpn/up-qbittorrent.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/with-contenv bashio -# shellcheck shell=bash -set -e - -WEBUI_PORT=${WEBUI_PORT:-8080} - -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}" diff --git a/qbittorrent/rootfs/etc/openvpn/up.sh b/qbittorrent/rootfs/etc/openvpn/up.sh deleted file mode 100755 index fe5da2bda..000000000 --- a/qbittorrent/rootfs/etc/openvpn/up.sh +++ /dev/null @@ -1,97 +0,0 @@ -#!/bin/sh -# shellcheck disable=SC2154,SC2004,SC2059,SC2086 - -# launch qbittorrent -/etc/openvpn/up-qbittorrent.sh "${4}" & - -# Copyright (c) 2006-2007 Gentoo Foundation -# Distributed under the terms of the GNU General Public License v2 -# Contributed by Roy Marples (uberlord@gentoo.org) - -# Setup our resolv.conf -# Vitally important that we use the domain entry in resolv.conf so we -# can setup the nameservers are for the domain ONLY in resolvconf if -# we're using a decent dns cache/forwarder like dnsmasq and NOT nscd/libc. -# nscd/libc users will get the VPN nameservers before their other ones -# and will use the first one that responds - maybe the LAN ones? -# non resolvconf users just the the VPN resolv.conf - -# FIXME:- if we have >1 domain, then we have to use search :/ -# We need to add a flag to resolvconf to say -# "these nameservers should only be used for the listed search domains -# if other global nameservers are present on other interfaces" -# This however, will break compatibility with Debians resolvconf -# A possible workaround would be to just list multiple domain lines -# and try and let resolvconf handle it - -if [ "${PEER_DNS}" != "no" ]; then - NS= - DOMAIN= - SEARCH= - i=1 - while true; do - eval opt=\$foreign_option_${i} - [ -z "${opt}" ] && break - if [ "${opt}" != "${opt#dhcp-option DOMAIN *}" ]; then - if [ -z "${DOMAIN}" ]; then - DOMAIN="${opt#dhcp-option DOMAIN *}" - else - SEARCH="${SEARCH}${SEARCH:+ }${opt#dhcp-option DOMAIN *}" - fi - elif [ "${opt}" != "${opt#dhcp-option DNS *}" ]; then - NS="${NS}nameserver ${opt#dhcp-option DNS *}\n" - fi - i=$((${i} + 1)) - done - - if [ -n "${NS}" ]; then - DNS="# Generated by openvpn for interface ${dev}\n" - if [ -n "${SEARCH}" ]; then - DNS="${DNS}search ${DOMAIN} ${SEARCH}\n" - elif [ -n "${DOMAIN}" ]; then - DNS="${DNS}domain ${DOMAIN}\n" - fi - DNS="${DNS}${NS}" - if [ -x /sbin/resolvconf ]; then - printf "${DNS}" | /sbin/resolvconf -a "${dev}" - else - # Preserve the existing resolv.conf - if [ -e /etc/resolv.conf ]; then - cp /etc/resolv.conf /etc/resolv.conf-"${dev}".sv - fi - printf "${DNS}" > /etc/resolv.conf - chmod 644 /etc/resolv.conf - fi - fi -fi - -# Below section is Gentoo specific -# Quick summary - our init scripts are re-entrant and set the RC_SVCNAME env var -# as we could have >1 openvpn service - -if [ -n "${RC_SVCNAME}" ]; then - # If we have a service specific script, run this now - if [ -x /etc/openvpn/"${RC_SVCNAME}"-up.sh ]; then - /etc/openvpn/"${RC_SVCNAME}"-up.sh "$@" - fi - - # Re-enter the init script to start any dependant services - if ! /etc/init.d/"${RC_SVCNAME}" --quiet status; then - export IN_BACKGROUND=true - if [ -d /var/run/s6/container_environment ]; then printf "%s" "true" > /var/run/s6/container_environment/IN_BACKGROUND; fi - printf "%s\n" "IN_BACKGROUND=\"true\"" >> ~/.bashrc - /etc/init.d/${RC_SVCNAME} --quiet start - fi -fi - -############### -# ALLOW WEBUI # -############### - -ip route add 10.0.0.0/8 via 172.30.32.1 -ip route add 192.168.0.0/16 via 172.30.32.1 -ip route add 172.16.0.0/12 via 172.30.32.1 - -exit 0 - -# vim: ts=4 : From 38c8929605bf7110facb23bdd1398ed3e3e1ac4e Mon Sep 17 00:00:00 2001 From: litinoveweedle <15144712+litinoveweedle@users.noreply.github.com> Date: Mon, 26 Jan 2026 18:11:13 +0100 Subject: [PATCH 04/35] vpn service script improvements & fixes simplified logging + notify user on the VPN behavior improved DNS servers handling + IPv6 defaults added Wireguard persistent-keepalive option fixed some execution bugg + typos --- qbittorrent/rootfs/usr/local/sbin/vpn | 167 ++++++++++++++------------ 1 file changed, 92 insertions(+), 75 deletions(-) diff --git a/qbittorrent/rootfs/usr/local/sbin/vpn b/qbittorrent/rootfs/usr/local/sbin/vpn index a4ff5dc84..80dc1eb65 100644 --- a/qbittorrent/rootfs/usr/local/sbin/vpn +++ b/qbittorrent/rootfs/usr/local/sbin/vpn @@ -1,11 +1,14 @@ #!/usr/bin/with-contenv bashio -# shellcheck shell=bas +# shellcheck shell=bash # --- WireGuard Specific Logic --- declare -A config config["MySelf"]="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")" +declare -a dns_servers_ipv4=() +declare -a dns_servers_ipv6=() + _parse_config() { local -n config_ref="$1" local config_file="$2" @@ -25,27 +28,42 @@ _parse_config() { } _parse_dns() { - local -a dns_servers=() - local dns_ip + local -a dns_conf=() + local -a dns_backup_ipv4=("8.8.8.8" "1.1.1.1") + local -a dns_backup_ipv6=("2001:4860:4860::8888" "2606:4700:4700::1111") - while IFS=',' read -r dns_ip; do - if _is_ip_address "${dns_ip}"; then - bashio::log.warning "Ignoring invalid DNS server address: ${dns_ip}" - continue + IFS=',' read -ra dns_conf <<< $(bashio::config 'DNS_server' | tr -d ' ') + if [ ${config["IPv4Enabled"]} = "true" ]; then + for dns_ip in "${dns_conf[@]}"; do + _is_ip_address "${dns_ip}" + local is_ip=$? + if [ "${is_ip}" -eq 1 ]; then + dns_servers_ipv4+=("${dns_ip}") + fi + done + if [ ${#dns_servers_ipv4[@]} -eq 0 ]; then + bashio::log.warning "No valid IPv4 DNS servers configured. Using addon defaults ${dns_backup_ipv4[@]}" + dns_servers_ipv4=("${dns_backup_ipv4[@]}") + fi + fi + if [ ${config["IPv6Enabled"]} = "true" ]; then + for dns_ip in "${dns_conf[@]}"; do + _is_ip_address "${dns_ip}" + local is_ip=$? + if [ "${is_ip}" -eq 2 ]; then + dns_servers_ipv6+=("${dns_ip}") + fi + done + if [ ${#dns_servers_ipv6[@]} -eq 0 ]; then + bashio::log.warning "No valid IPv6 DNS servers configured. Using addon defaults ${dns_backup_ipv6[@]}" + dns_servers_ipv6=("${dns_backup_ipv6[@]}") fi - dns_servers+=("${dns_ip}") - done <<< $(bashio::config 'DNS_server' | tr -d ' ') - - if [ ${#dns_servers[@]} -eq 0 ]; then - bashio::log.warning "No valid DNS servers configured. Using addon defaults." - dns_servers=("8.8.8.8" "1.1.1.1") fi - config["DnsServers"]="${dns_servers[*]}" } _cmd() { cmd="$1" - bashio::log.info "Executing command: ${cmd}" + bashio::log.debug "Executing command: ${cmd}" eval "${cmd}" } @@ -72,32 +90,19 @@ _resolvconf() { bashio::log.warning "No original resolv.conf backup found. Leaving as is." fi elif [ "${mode}" = "update" ]; then - bashio::log.info "Updating ${resolv_conf} with DNS servers: ${config["DnsServers"]}" + bashio::log.info "Updating ${resolv_conf} with VPN DNS servers." if ! bashio::fs.file_exists "${resolv_backup}"; then + bashio::log.debug "Creating backup of original resolv.conf at ${resolv_backup}" cp "${resolv_conf}" "${resolv_backup}" 2>/dev/null || true fi - bashio::log.warn "Overriding ${resolv_conf} with DNS servers: ${config["DnsServers"]}" - local valid_dns="false" + bashio::log.debug "Updating ${resolv_conf} with DNS servers: ${dns_servers_ipv4[*]} ${dns_servers_ipv6[*]}" { + echo "# Generated by vpn script" local dns_ip - for dns_ip in ${config["DnsServers"]}; do - _is_ip_address "${dns_ip}" - local is_ip=$? - if [ "${is_ip}" -eq 1 ] && [ ${config["IPv4Enabled"]} = "true" ]; then - echo "nameserver ${dns_ip}" - valid_dns="true" - elif [ "${is_ip}" -eq 2 ] && [ "${config["IPv6Enabled"]}" = "true" ]; then - echo "nameserver ${dns_ip}" - valid_dns="true" - else - bashio::log.warning "Ignoring invalid DNS server address: ${dns_ip}" - continue - fi + for dns_ip in ${dns_servers_ipv4[@]} ${dns_servers_ipv6[@]}; do + echo "nameserver ${dns_ip}" done } > "${resolv_conf}" - if [ "${valid_dns}" = "false" ]; then - bashio::exit.nok "No valid DNS servers could be written to ${resolv_conf}." - fi else bashio::exit.nok "Invalid resolvconf mode specified. Use 'update' or 'reset'." fi @@ -127,9 +132,14 @@ _resolve_hostname() { } _routing_add() { + bashio::log.info "Adding routing rules for VPN interface ${config["Interface"]}..." + local local_ipv4=$(ip addr show ${config["Interface"]} | grep 'inet ' | awk '{print $2}' | cut -d'/' -f1) local local_ipv6=$(ip addr show ${config["Interface"]} | grep 'inet6 ' | awk '{print $2}' | cut -d'/' -f1) - local ipv4, ipv6 + local ipv4 + local ipv6 + + # add routing rules for local IPs for ipv4 in ${local_ipv4}; do config["IPv4Enabled"]="true" _cmd "ip -4 route add default dev ${config["Interface"]} table ${config["Table"]}" || return 1 @@ -141,27 +151,26 @@ _routing_add() { _cmd "ip -6 rule add priority 1 from ${ipv6} table ${config["Table"]}" || return 1 done - local dns_ip - for dns_ip in ${config["DnsServers"]}; do - _is_ip_address "${dns_ip}" - local is_ip=$? - if [ "${is_ip}" -eq 0 ]; then - bashio::log.warning "Ignoring invalid DNS server address: ${dns_ip}" - continue - elif [ "${is_ip}" -eq 1 ] && [ ${config["IPv4Enabled"]} = "true" ]; then - #_cmd "ip -4 route add ${dns_ip} dev ${config["Interface"]}" || return 1 - _cmd "ip -4 rule add priority 1 to ${dns_ip} table ${config["Table"]}" || return 1 - elif [ "${is_ip}" -eq 2 ] && [ "${config["IPv6Enabled"]}" = "true" ]; then - #_cmd "ip -6 route add ${dns_ip} dev ${config["Interface"]}" || return 1 - _cmd "ip -6 rule add priority 1 to ${dns_ip} table ${config["Table"]}" || return 1 - else - bashio::log.warning "Failed to add route for DNS server: ${dns_ip}" - fi + # get valid DNS servers + _parse_dns + + # add routing rules for DNS servers + for dns_ip in "${dns_servers_ipv4[@]}"; do + #_cmd "ip -4 route add ${dns_ip} dev ${config["Interface"]}" || return 1 + _cmd "ip -4 rule add priority 1 to ${dns_ip} table ${config["Table"]}" || return 1 done + for dns_ip in "${dns_servers_ipv6[@]}"; do + #_cmd "ip -6 route add ${dns_ip} dev ${config["Interface"]}" || return 1 + _cmd "ip -6 rule add priority 1 to ${dns_ip} table ${config["Table"]}" || return 1 + done + + # Update resolv.conf with VPN DNS servers _resolvconf "update" } _routing_del() { + bashio::log.info "Removing routing rules for VPN interface ${config["Interface"]}..." + _resolvconf "reset" while _cmd "ip -4 rule del priority 1 from all table ${config["Table"]} 2>/dev/null"; do :; done while _cmd "ip -4 rule del priority 1 to all table ${config["Table"]} 2>/dev/null"; do :; done @@ -172,14 +181,19 @@ _routing_del() { } _wireguard_up() { - bashio::log.warn "Bringing up Wireguard interface on ${config["Interface"]}..." - bashio::log.warn "Using Wireguard configuration file: ${config["ConfigFile"]}" - bashio::log.warn "This script force Wireguard to ignore any routes and DNS settings." - bashio::log.warn "Default route will be inserted into custom routing table: ${config["Table"]}" - bashio::log.warn "This routing table will be used for traffic from the VPN interface and to configured DNS servers." - bashio::log.warn "Qbittorrent bittorrent client shall be set to use the VPN interface ${config["Interface"]} only." + bashio::log.warning "This script force Wireguard to ignore any routes and DNS settings." + bashio::log.warning "Default route will be inserted into custom routing table: ${config["Table"]}" + bashio::log.warning "This routing table will be used for traffic from the VPN interface and to the configured DNS servers." + bashio::log.warning "Qbittorrent bittorrent client shall be set to use the VPN interface ${config["Interface"]} only." - _cmd "ip link add dev ${config["Interface"]} type wireguard" || return 1 + for key in "Interface" "ListenPort" "PrivateKey" "PublicKey" "EndpointIP" "EndpointPort" ; do + if [ ! -v config[$key] ] || [ -z "${config[$key]}" ]; then + bashio::log.error "Missing required WireGuard configuration parameter: ${key}" + return 1 + fi + done + + _cmd "ip link add ${config["Interface"]} type wireguard" || return 1 local allowed_ips="" for local_ip in ${config["Address"]}; do _is_ip_address "${local_ip}" @@ -199,10 +213,12 @@ _wireguard_up() { bashio::log.error "No valid local IP addresses configured." return 1 fi + _cmd "wg set ${config["Interface"]} listen-port ${config["ListenPort"]} private-key ${config["PrivateKey"]}" || return 1 - _cmd "wg set ${config["Interface"]} peer ${config["PublicKey"]} endpoint ${config["EndpointIP"]}:${config["EndpointPort"]} allowed-ips ${allowed_ips}" || return 1 - if [ -n "${config["PersistentKeepalive"]}" ]; then - _cmd "wg set ${config["Interface"]} peer ${config["PublicKey"]} persistent-keepalive ${config["PersistentKeepalive"]}" || return 1 + if [ -v config["PersistentKeepalive"] ] && [ -n "${config["PersistentKeepalive"]}" ]; then + _cmd "wg set ${config["Interface"]} peer ${config["PublicKey"]} endpoint ${config["EndpointIP"]}:${config["EndpointPort"]} allowed-ips ${allowed_ips} persistent-keepalive ${config["PersistentKeepalive"]}" || return 1 + else + _cmd "wg set ${config["Interface"]} peer ${config["PublicKey"]} endpoint ${config["EndpointIP"]}:${config["EndpointPort"]} allowed-ips ${allowed_ips}" || return 1 fi _cmd "ip link set ${config["Interface"]} up" || return 1 _routing_add @@ -211,7 +227,7 @@ _wireguard_up() { _wireguard_down() { _routing_del _cmd "ip link set ${config["Interface"]} down" 2>/dev/null || true - _cmd "ip link del dev ${config["Interface"]}" 2>/dev/null || true + _cmd "ip link del ${config["Interface"]}" 2>/dev/null || true } wireguard() { @@ -235,6 +251,8 @@ wireguard() { bashio::exit.nok 'WireGuard runtime configuration not prepared. Please restart the add-on.' fi + bashio::log.info "Using Wireguard configuration file: ${config_file}" + _parse_config config "${config_file}" config["Interface"]="${interface}" config["ConfigFile"]="${config_file}" @@ -252,7 +270,7 @@ wireguard() { config["PrivateKey"]="${WIREGUARD_STATE_DIR}/privatekey" if [ "${mode}" = "up" ]; then - bashio::log.info "Starting WireGuard interface ${config["Interface"]}..." + bashio::log.info "Starting WireGuard on interface ${config["Interface"]}..." if _is_ip_address ${config["EndpointHost"]}; then local endpoint_ips=$(_resolve_hostname ${config["EndpointHost"]}) if [ ${#endpoint_ips[@]} -eq 0 ]; then @@ -270,7 +288,7 @@ wireguard() { _wireguard_down done else - bashio::log.info "WireGuard endpoint ${config["EndpointHost"]} is a valid IP address. Using as is." + bashio::log.debug "WireGuard endpoint ${config["EndpointHost"]} is a valid IP address. Using as is." config["EndpointIP"]="${config["EndpointHost"]}" if _wireguard_up; then bashio::log.info "WireGuard interface ${config["Interface"]} is up." @@ -280,9 +298,9 @@ wireguard() { _wireguard_down fi elif [ "${mode}" = "down" ]; then - bashio::log.info "Stopping WireGuard interface ${config["Interface"]}..." + bashio::log.info "Stopping WireGuard on interface ${config["Interface"]}..." _wireguard_down - bashio::log.info "WireGuard interface ${config["Interface"]} is down." + bashio::log.info "WireGuard on interface ${config["Interface"]} is down." bashio::exit.ok 'WireGuard stopped.' else bashio::log.error "Invalid WireGuard mode specified. Use 'up' or 'down'." @@ -293,12 +311,10 @@ wireguard() { } _openvpn_up() { - bashio::log.warn "Bringing up OpenVPN interface on ${config["Interface"]}..." - bashio::log.warn "Using OpenVPN configuration file: ${config["ConfigFile"]}" - bashio::log.warn "This script force OpenvPN to ignore any routes and DNS settings pushed by the server." - bashio::log.warn "Default route will be inserted into custom routing table: ${config["Table"]}" - bashio::log.warn "This routing table will be used for traffic from the VPN interface and to configured DNS servers." - bashio::log.warn "Qbittorrent bittorrent client shall be set to use the VPN interface ${config["Interface"]} only." + bashio::log.warning "This script force OpenvPN to ignore any routes and DNS settings pushed by the server." + bashio::log.warning "Default route will be inserted into custom routing table: ${config["Table"]}" + bashio::log.warning "This routing table will be used for traffic from the VPN interface and to the configured DNS servers." + bashio::log.warning "Qbittorrent bittorrent client shall be set to use the VPN interface ${config["Interface"]} only." # Register this script as OpenVPN up/down handlers to manage routing echo "${config["MySelf"]} openvpn postup" > ${config["PostUpScript"]} @@ -350,6 +366,8 @@ openvpn() { bashio::exit.nok 'OpenVPN runtime configuration not prepared. Please restart the add-on.' fi + bashio::log.warning "Using OpenVPN configuration file: ${config_file}" + _parse_config config "${config_file}" config["Interface"]="${interface}" config["ConfigFile"]="${config_file}" @@ -359,11 +377,11 @@ openvpn() { if [ "${mode}" = "up" ]; then # register up and down scripts - bashio::log.info "Starting OpenVPN with configuration file: ${config_file}..." + bashio::log.info "Starting OpenVPN on interface ${config["Interface"]}..." _openvpn_up bashio::exit.ok 'OpenVPN started.' elif [ "${mode}" = "down" ]; then - bashio::log.info "Stopping OpenVPN..." + bashio::log.info "Stopping OpenVPN on interface ${config["Interface"]}..." _openvpn_down bashio::exit.ok 'OpenVPN stopped.' elif [ "${mode}" = "postup" ]; then @@ -382,7 +400,6 @@ if [ $# -ne 2 ]; then bashio::log.error "Invalid number of arguments. Usage: vpn.sh " bashio::exit.nok 'VPN start failed.' fi -_parse_dns if [[ "$1" == "wireguard" ]]; then wireguard "$2" elif [[ "$1" == "openvpn" ]]; then From af403e5eae68a3f635202983f21b6dee6881faad Mon Sep 17 00:00:00 2001 From: litinoveweedle <15144712+litinoveweedle@users.noreply.github.com> Date: Mon, 26 Jan 2026 18:26:14 +0100 Subject: [PATCH 05/35] fix OpenVPN config suffix --- qbittorrent/config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qbittorrent/config.yaml b/qbittorrent/config.yaml index 9bbae572b..7c8ab5f4a 100644 --- a/qbittorrent/config.yaml +++ b/qbittorrent/config.yaml @@ -130,7 +130,7 @@ schema: localdisks: str? networkdisks: str? openvpn_alt_mode: bool? - openvpn_config: match(^\w+\.conf$)? + openvpn_config: match(^\w+\.ovpn$)? openvpn_enabled: bool? openvpn_password: str? openvpn_username: str? From b001f1ebca56ad3b55d7f9e149f8dc30d7e6eade Mon Sep 17 00:00:00 2001 From: litinoveweedle <15144712+litinoveweedle@users.noreply.github.com> Date: Mon, 26 Jan 2026 20:24:29 +0100 Subject: [PATCH 06/35] Add wireguard MTU option + fix OpenVPN suffix to both conf and ovn --- qbittorrent/config.yaml | 2 +- qbittorrent/rootfs/usr/local/sbin/vpn | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/qbittorrent/config.yaml b/qbittorrent/config.yaml index 7c8ab5f4a..432b70232 100644 --- a/qbittorrent/config.yaml +++ b/qbittorrent/config.yaml @@ -130,7 +130,7 @@ schema: localdisks: str? networkdisks: str? openvpn_alt_mode: bool? - openvpn_config: match(^\w+\.ovpn$)? + openvpn_config: match(^\w+\.(ovpn|conf)$)? openvpn_enabled: bool? openvpn_password: str? openvpn_username: str? diff --git a/qbittorrent/rootfs/usr/local/sbin/vpn b/qbittorrent/rootfs/usr/local/sbin/vpn index 80dc1eb65..b6bf9e361 100644 --- a/qbittorrent/rootfs/usr/local/sbin/vpn +++ b/qbittorrent/rootfs/usr/local/sbin/vpn @@ -220,6 +220,11 @@ _wireguard_up() { else _cmd "wg set ${config["Interface"]} peer ${config["PublicKey"]} endpoint ${config["EndpointIP"]}:${config["EndpointPort"]} allowed-ips ${allowed_ips}" || return 1 fi + + if [ -v config["MTU"] ] && [ -n "${config["MTU"]}" ]; then + _cmd "ip link set ${config["Interface"]} mtu ${config["MTU"]}" || return 1 + fi + _cmd "ip link set ${config["Interface"]} up" || return 1 _routing_add } From 80f7efb0137779cd314fd41f720a08d7523f2ab2 Mon Sep 17 00:00:00 2001 From: litinoveweedle <15144712+litinoveweedle@users.noreply.github.com> Date: Tue, 27 Jan 2026 12:15:16 +0100 Subject: [PATCH 07/35] overhaul cont-init vpn scripts --- qbittorrent/config.yaml | 1 - qbittorrent/rootfs/usr/local/sbin/vpn | 31 +++++++++++++++------------ 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/qbittorrent/config.yaml b/qbittorrent/config.yaml index 432b70232..d6be10613 100644 --- a/qbittorrent/config.yaml +++ b/qbittorrent/config.yaml @@ -129,7 +129,6 @@ schema: keyfile: str localdisks: str? networkdisks: str? - openvpn_alt_mode: bool? openvpn_config: match(^\w+\.(ovpn|conf)$)? openvpn_enabled: bool? openvpn_password: str? diff --git a/qbittorrent/rootfs/usr/local/sbin/vpn b/qbittorrent/rootfs/usr/local/sbin/vpn index b6bf9e361..8bc0ce64e 100644 --- a/qbittorrent/rootfs/usr/local/sbin/vpn +++ b/qbittorrent/rootfs/usr/local/sbin/vpn @@ -322,26 +322,26 @@ _openvpn_up() { bashio::log.warning "Qbittorrent bittorrent client shall be set to use the VPN interface ${config["Interface"]} only." # Register this script as OpenVPN up/down handlers to manage routing - echo "${config["MySelf"]} openvpn postup" > ${config["PostUpScript"]} + echo '#!/bin/bash' > ${config["PostUpScript"]} + echo "${config["MySelf"]} openvpn postup" >> ${config["PostUpScript"]} chmod 755 ${config["PostUpScript"]} - echo "${config["MySelf"]} openvpn postdown" > ${config["PostDownScript"]} + echo '#!/bin/bash' > ${config["PostDownScript"]} + echo "${config["MySelf"]} openvpn postdown" >> ${config["PostDownScript"]} chmod 755 ${config["PostDownScript"]} # Start OpenVPN in the background - # (maybe use setsid instead of nohup to detach completely?) - nohup /usr/sbin/openvpn \ + _cmd "/usr/sbin/openvpn --config "${config["ConfigFile"]}" \ - --script-security 2 \ + --script-security 2 --daemon --log /dev/null \ + --auth-user-pass "${OPENVPN_STATE_DIR}/credentials.conf" \ + --auth-retry none \ --up ${config["PostUpScript"]} \ + --up_delay 5 \ --down ${config["PostDownScript"]} \ + --down-delay 5 \ + --up-restart \ --route-nopull \ - --pull-filter ignore "route" \ - --pull-filter ignore "redirect-gateway" \ - --pull-filter ignore "dhcp-option DNS" \ - --pull-filter ignore "route-ipv6" \ - --pull-filter ignore "redirect-gateway ipv6" \ - --pull-filter ignore "dhcp-option DNS6" \ - & + --route-noexec" || return 1 } _openvpn_down() { @@ -383,8 +383,11 @@ openvpn() { if [ "${mode}" = "up" ]; then # register up and down scripts bashio::log.info "Starting OpenVPN on interface ${config["Interface"]}..." - _openvpn_up - bashio::exit.ok 'OpenVPN started.' + if _openvpn_up; then + bashio::exit.ok 'OpenVPN started.' + fi + bashio::log.error 'OpenVPN failed to establish connection.' + _openvpn_down elif [ "${mode}" = "down" ]; then bashio::log.info "Stopping OpenVPN on interface ${config["Interface"]}..." _openvpn_down From 22dc8a835159cba91c0543fc07f401fb6cc760ec Mon Sep 17 00:00:00 2001 From: litinoveweedle <15144712+litinoveweedle@users.noreply.github.com> Date: Tue, 27 Jan 2026 12:17:30 +0100 Subject: [PATCH 08/35] overhaul of the cont-init vpn scripts --- .../rootfs/etc/cont-init.d/93-openvpn.sh | 361 +++++------------- .../rootfs/etc/cont-init.d/94-wireguard.sh | 79 ++-- 2 files changed, 150 insertions(+), 290 deletions(-) diff --git a/qbittorrent/rootfs/etc/cont-init.d/93-openvpn.sh b/qbittorrent/rootfs/etc/cont-init.d/93-openvpn.sh index 0bab9a487..787d3c6b0 100755 --- a/qbittorrent/rootfs/etc/cont-init.d/93-openvpn.sh +++ b/qbittorrent/rootfs/etc/cont-init.d/93-openvpn.sh @@ -2,263 +2,112 @@ # shellcheck shell=bash set -e -declare openvpn_config +OPENVPN_STATE_DIR="/var/run/openvpn" +QBT_CONFIG_FILE="/config/qBittorrent/qBittorrent.conf" +declare openvpn_config="" +declare openvpn_runtime_config="" +declare interface_name="" declare openvpn_username declare openvpn_password -QBT_CONFIG_FILE="/config/qBittorrent/qBittorrent.conf" - -if bashio::config.true 'openvpn_enabled'; then - - bashio::log.info "----------------------------" - bashio::log.info "Openvpn enabled, configuring" - bashio::log.info "----------------------------" - - # Function to check for files path - function check_path() { - - # Get variable - file="$1" - - # Double check exists - if [ ! -f "$file" ]; then - bashio::warning "$file not found" - return 1 - fi - - # Check each lines - cp "$file" /tmpfile - line_number=0 - while read -r line; do - # Increment the line number - ((line_number = line_number + 1)) - - # Check if lines starting with auth-user-pass have a valid argument - ################################################################### - if [[ "$line" == "auth-user-pass"* ]]; then - # Extract the second argument - file_name="$(echo "$line" | awk -F' ' '{print $2}')" - # If second argument is null or - - if [ -z "$file_name" ] || [[ "$file_name" == -* ]]; then - # Insert to explain why a comment is made - sed -i "${line_number}i # The following line is commented out as does not contain a valid argument" "$file" - # Increment as new line added - ((line_number = line_number + 1)) - # Comment out the line - sed -i "${line_number}s/^/# /" "$file" - # Go to next line - continue - fi - fi - - # Check if the line contains a txt file - ####################################### - if [[ ! $line =~ ^"#" ]] && [[ ! $line =~ ^";" ]] && [[ ! $line =~ ^"remote" ]] && [[ "$line" == *" "*"."* ]] || [[ "$line" == "auth-user-pass"* ]]; then - # Extract the txt file name from the line - file_name="$(echo "$line" | awk -F' ' '{print $2}')" - # if contains only numbers and dots it is likely an ip, don't check it - if [[ "$file_name" =~ ^[0-9\.]+$ ]]; then - continue - fi - # Check if the txt file exists - if [[ "$file_name" != *"/etc/openvpn/credentials"* ]] && [ ! -f "$file_name" ]; then - # Check if the txt file exists in the /config/openvpn/ directory - if [ -f "/config/openvpn/${file_name##*/}" ]; then - # Append /config/openvpn/ in front of the original txt file in the ovpn file - sed -i "${line_number}s|$file_name|/config/openvpn/${file_name##*/}|" "$file" - # Print a success message - bashio::log.warning "Appended /config/openvpn/ to ${file_name##*/} in $file" - else - # Print an error message - bashio::log.warning "$file_name is referenced in your ovpn file but does not exist, and can't be found either in the /config/openvpn/ directory" - fi - fi - fi - done < /tmpfile - rm /tmpfile - - # Standardize lf - dos2unix "$file" - - # Remove custom up & down - sed -i '/^up /s/^/#/' "$file" - sed -i '/^down /s/^/#/' "$file" - - # Remove blank lines - sed -i '/^[[:blank:]]*$/d' "$file" - - # Ensure config ends with a line feed - sed -i "\$q" "$file" - - # Correct paths - sed -i "s=/etc/openvpn=/config/openvpn=g" "$file" - sed -i "s=/config/openvpn/credentials=/etc/openvpn/credentials=g" "$file" - - } - - ##################### - # CONFIGURE OPENVPN # - ##################### - - # If openvpn_config option used - if bashio::config.has_value "openvpn_config"; then - openvpn_config=$(bashio::config 'openvpn_config') - # If file found - if [ -f /config/openvpn/"$openvpn_config" ]; then - # If correct type - if [[ "$openvpn_config" == *".ovpn" ]] || [[ "$openvpn_config" == *".conf" ]]; then - echo "... configured ovpn file : using /addon_configs/$HOSTNAME/openvpn/$openvpn_config" - else - bashio::exit.nok "Configured ovpn file : $openvpn_config is set but does not end by .ovpn ; it can't be used!" - fi - else - bashio::exit.nok "Configured ovpn file : $openvpn_config not found! Are you sure you added it in /addon_configs/$HOSTNAME/openvpn using the Filebrowser addon ?" - fi - - # If openvpn_config not set, but folder is not empty - elif ls /config/openvpn/*.ovpn > /dev/null 2>&1; then - # Look for openvpn files - # Wildcard search for openvpn config files and store results in array - mapfile -t VPN_CONFIGS < <(find /config/openvpn -maxdepth 1 -name "*.ovpn" -print) - # Choose random config - VPN_CONFIG="${VPN_CONFIGS[$RANDOM % ${#VPN_CONFIGS[@]}]}" - # Get the VPN_CONFIG name without the path and extension - openvpn_config="${VPN_CONFIG##*/}" - echo "... Openvpn enabled, but openvpn_config option empty. Selecting a random ovpn file : ${openvpn_config}. Other available files :" - printf '%s\n' "${VPN_CONFIGS[@]}" - # If openvpn_enabled set, config not set, and openvpn folder empty - else - bashio::exit.nok "openvpn_enabled is set, however, your openvpn folder is empty ! Are you sure you added it in /addon_configs/$HOSTNAME/openvpn using the Filebrowser addon ?" - fi - - # Send to openvpn script - sed -i "s|/config/openvpn/config.ovpn|/config/openvpn/$openvpn_config|g" /etc/s6-overlay/s6-rc.d/svc-qbittorrent/run - - # Check path - check_path /config/openvpn/"${openvpn_config}" - - # Set credentials - if bashio::config.has_value "openvpn_username"; then - openvpn_username=$(bashio::config 'openvpn_username') - echo "${openvpn_username}" > /etc/openvpn/credentials - else - bashio::exit.nok "Openvpn is enabled, but openvpn_username option is empty! Exiting" - fi - if bashio::config.has_value "openvpn_password"; then - openvpn_password=$(bashio::config 'openvpn_password') - echo "${openvpn_password}" >> /etc/openvpn/credentials - else - bashio::exit.nok "Openvpn is enabled, but openvpn_password option is empty! Exiting" - fi - - # Add credentials file - if grep -q ^auth-user-pass /config/openvpn/"$openvpn_config"; then - # Credentials specified are they custom ? - file_name="$(sed -n "/^auth-user-pass/p" /config/openvpn/"$openvpn_config" | awk -F' ' '{print $2}')" - file_name="${file_name:-null}" - if [[ "$file_name" != *"/etc/openvpn/credentials"* ]] && [[ "$file_name" != "null" ]]; then - if [ -f "$file_name" ]; then - # If credential specified, exists, and is not the addon default - bashio::log.warning "auth-user-pass specified in the ovpn file, addon username and passwords won't be used !" - else - # Credential referenced but doesn't exist - bashio::log.warning "auth-user-pass $file_name is referenced in your ovpn file but does not exist, and can't be found either in the /config/openvpn/ directory. The addon will attempt to use it's own username and password instead." - # Comment previous lines - sed -i '/^auth-user-pass/i # specified auth-user-pass file not found, disabling' /config/openvpn/"$openvpn_config" - sed -i '/^auth-user-pass/s/^/#/' /config/openvpn/"$openvpn_config" - # No credentials specified, using addons username and password - echo "# Please do not remove the line below, it allows using the addon username and password" >> /config/openvpn/"$openvpn_config" - echo "auth-user-pass /etc/openvpn/credentials" >> /etc/openvpn/"$openvpn_config" - fi - else - # Standardize just to be sure - sed -i "/\/etc\/openvpn\/credentials/c auth-user-pass \/etc\/openvpn\/credentials" /config/openvpn/"$openvpn_config" - fi - else - # No credentials specified, using addons username and password - echo "# Please do not remove the line below, it allows using the addon username and password" >> /config/openvpn/"$openvpn_config" - echo "auth-user-pass /etc/openvpn/credentials" >> /config/openvpn/"$openvpn_config" - fi - - # Permissions - chmod 755 /config/openvpn/* - chmod 755 /etc/openvpn/* - chmod 600 /etc/openvpn/credentials - - echo "... openvpn correctly set, qbittorrent will run tunnelled through openvpn" - - ######################### - # CONFIGURE QBITTORRENT # - ######################### - - # WITH CONTAINER BINDING - ######################### - # If alternative mode enabled, bind container - if bashio::config.true 'openvpn_alt_mode'; then - echo "Using container binding" - - # Remove interface - echo "... deleting previous interface settings" - sed -i '/Interface/d' "$QBT_CONFIG_FILE" - - # Modify ovpn config - if grep -q route-nopull /config/openvpn/"$openvpn_config"; then - echo "... removing route-nopull from your config.ovpn" - sed -i '/route-nopull/d' /config/openvpn/"$openvpn_config" - fi - - # Exit - exit 0 - fi - - # WITH INTERFACE BINDING - ######################### - # Connection with interface binding - echo "Using interface binding in the qBittorrent app" - - # Define preferences line - cd /config/qBittorrent/ || exit 1 - - # If qBittorrent.conf exists - if [ -f "$QBT_CONFIG_FILE" ]; then - # Remove previous line and bind tun0 - echo "... deleting previous interface settings" - sed -i '/Interface/d' "$QBT_CONFIG_FILE" - - # Bind tun0 - echo "... binding tun0 interface in qBittorrent configuration" - sed -i "/\[Preferences\]/ i\Connection\\\Interface=tun0" "$QBT_CONFIG_FILE" - sed -i "/\[Preferences\]/ i\Connection\\\InterfaceName=tun0" "$QBT_CONFIG_FILE" - - # Add to ongoing session - sed -i "/\[BitTorrent\]/a \Session\\\Interface=tun0" "$QBT_CONFIG_FILE" - sed -i "/\[BitTorrent\]/a \Session\\\InterfaceName=tun0" "$QBT_CONFIG_FILE" - - else - bashio::log.error "qBittorrent config file doesn't exist, openvpn must be added manually to qbittorrent options " - exit 1 - fi - - # Modify ovpn config - if ! grep -q route-nopull /config/openvpn/"$openvpn_config"; then - echo "... adding route-nopull to your config.ovpn" - sed -i "1a route-nopull" /config/openvpn/"$openvpn_config" - fi - -else - - ################## - # REMOVE OPENVPN # - ################## - - if ! bashio::config.true 'wireguard_enabled'; then - # Ensure no redirection by removing the direction tag when no VPN is used - if [ -f "$QBT_CONFIG_FILE" ]; then - sed -i '/Interface/d' "$QBT_CONFIG_FILE" - fi - bashio::log.info "Direct connection without VPN enabled" - else - bashio::log.info "OpenVPN disabled. WireGuard handling network binding." - fi - +if bashio::file_exists "${OPENVPN_STATE_DIR}"; then + bashio::log.warning "Previous OpenVPN state directory found, cleaning up." + rm -Rf "${OPENVPN_STATE_DIR}" fi + +if ! bashio::config.true 'openvpn_enabled'; then + bashio::exit.ok 'OpenVPN is disabled.' +elif bashio::config.true 'wireguard_enabled'; then + bashio::exit.nok 'OpenVPN and WireGuard cannot be enabled simultaneously. Disable one of them.' +fi + +mkdir -p "${OPENVPN_STATE_DIR}" + +bashio::log.info "----------------------------" +bashio::log.info "Openvpn enabled, configuring" +bashio::log.info "----------------------------" + +# Set credentials +if bashio::config.has_value "openvpn_username"; then + openvpn_username=$(bashio::config 'openvpn_username') +else + bashio::exit.nok "Openvpn is enabled, but openvpn_username option is empty! Exiting" +fi +if bashio::config.has_value "openvpn_password"; then + openvpn_password=$(bashio::config 'openvpn_password') +else + bashio::exit.nok "Openvpn is enabled, but openvpn_password option is empty! Exiting" +fi + +echo -e "${openvpn_username}\n${openvpn_password}" > "${OPENVPN_STATE_DIR}/credentials.conf" +chmod 600 "${OPENVPN_STATE_DIR}/credentials.conf" + +if bashio::config.has_value 'openvpn_config'; then + openvpn_config="$(bashio::config 'openvpn_config')" + openvpn_config="${openvpn_config##*/}" + if [[ -z "${openvpn_config}" ]]; then + bashio::log.info 'openvpn_config option left empty. Attempting automatic selection.' + mapfile -t configs < <(find /config/openvpn -maxdepth 1 -type f -name '*.conf' -o -name '*.ovpn' -print) + if [ "${#configs[@]}" -eq 0 ]; then + bashio::exit.nok 'OpenVPN is enabled but no .conf or .ovpn file was found in /config/openvpn.' + elif [ "${#configs[@]}" -eq 1 ]; then + openvpn_config="${configs[0]}" + bashio::log.info "OpenVPN configuration not specified. Using ${openvpn_config##*/}." + elif bashio::fs.file_exists '/config/openvpn/config.conf'; then + openvpn_config='/config/openvpn/config.conf' + bashio::log.info 'Using default OpenVPN configuration config.conf.' + else + bashio::exit.nok "Multiple OpenVPN configuration files detected. Please set the 'openvpn_config' option." + fi + elif bashio::fs.file_exists "/config/openvpn/${openvpn_config}"; then + openvpn_config="/config/openvpn/${openvpn_config}" + else + bashio::exit.nok "OpenVPN configuration '/config/openvpn/${openvpn_config}' not found." + fi +fi + +interface_name="$(sed -n "/^dev tun/p" "${openvpn_config}" | awk -F' ' '{print $2}')" +if [[ -z "${interface_name}" ]]; then + bashio::exit.nok "OpenVPN configuration '/config/openvpn/${openvpn_config}' miss device directive." +elif [[ ${interface_name} = "tun" ]]; then + interface_name='tun0' +elif [[ ${interface_name} = "tap" ]]; then + interface_name='tap0' +fi + +openvpn_runtime_config="${OPENVPN_STATE_DIR}/${interface_name}.conf" + +cp "${openvpn_config}" "${openvpn_runtime_config}" +chmod 600 "${openvpn_runtime_config}" + +dos2unix "${openvpn_runtime_config}" >/dev/null 2>&1 || true +sed -i '/^[[:space:]]*[;#]/d' "${openvpn_runtime_config}" +sed -i 's/#.*//' "${openvpn_runtime_config}" +sed -i '/^[[:space:]]*$/d' "${openvpn_runtime_config}" +sed -i '/^[[:blank:]]*$/d' "${openvpn_runtime_config}" +sed -i '/^up/d' "${openvpn_runtime_config}" +sed -i '/^down/d' "${openvpn_runtime_config}" +sed -i '/^route/d' "${openvpn_runtime_config}" +sed -i '/^auth-user-pass /d' "${openvpn_runtime_config}" +sed -i '/^cd /d' "${openvpn_runtime_config}" +sed -i '/^chroot /d' "${openvpn_runtime_config}" +sed -i '\$q' "${openvpn_runtime_config}" + +bashio::log.info 'Prepared OpenVPN runtime configuration for initial connection attempt.' + +echo "${openvpn_runtime_config}" > "${OPENVPN_STATE_DIR}/config" +echo "${interface_name}" > "${OPENVPN_STATE_DIR}/interface" + +bashio::log.info "Using interface binding in the qBittorrent app" + +if bashio::fs.file_exists "${QBT_CONFIG_FILE}"; then + sed -i '/Interface/d' "${QBT_CONFIG_FILE}" + sed -i "/\\[Preferences\\]/ i\\Connection\\\\Interface=${interface_name}" "${QBT_CONFIG_FILE}" + sed -i "/\\[Preferences\\]/ i\\Connection\\\\InterfaceName=${interface_name}" "${QBT_CONFIG_FILE}" + sed -i "/\\[BitTorrent\\]/a \\Session\\\\Interface=${interface_name}" "${QBT_CONFIG_FILE}" + sed -i "/\\[BitTorrent\\]/a \\Session\\\\InterfaceName=${interface_name}" "${QBT_CONFIG_FILE}" +else + bashio::log.warning "qBittorrent config file not found. Bind the client manually to interface ${interface_name}." +fi + +bashio::log.info "OpenVPN prepared with interface ${interface_name} using configuration ${openvpn_config##*/}." diff --git a/qbittorrent/rootfs/etc/cont-init.d/94-wireguard.sh b/qbittorrent/rootfs/etc/cont-init.d/94-wireguard.sh index 056f704ba..dcec326de 100755 --- a/qbittorrent/rootfs/etc/cont-init.d/94-wireguard.sh +++ b/qbittorrent/rootfs/etc/cont-init.d/94-wireguard.sh @@ -6,52 +6,49 @@ WIREGUARD_STATE_DIR="/var/run/wireguard" QBT_CONFIG_FILE="/config/qBittorrent/qBittorrent.conf" declare wireguard_config="" declare wireguard_runtime_config="" -declare configured_name +declare interface_name="" -mkdir -p "${WIREGUARD_STATE_DIR}" - -if ! bashio::config.true 'wireguard_enabled'; then - rm -f "${WIREGUARD_STATE_DIR}/config" "${WIREGUARD_STATE_DIR}/interface" - exit 0 +if bashio::file_exists "${WIREGUARD_STATE_DIR}"; then + bashio::log.warning "Previous WireGuard state directory found, cleaning up." + rm -Rf "${WIREGUARD_STATE_DIR}" fi -if bashio::config.true 'openvpn_enabled'; then +if ! bashio::config.true 'wireguard_enabled'; then + bashio::exit.ok 'WireGuard is disabled.' +elif bashio::config.true 'openvpn_enabled'; then bashio::exit.nok 'OpenVPN and WireGuard cannot be enabled simultaneously. Disable one of them.' fi -if bashio::config.true 'openvpn_alt_mode'; then - bashio::log.warning 'The openvpn_alt_mode option is ignored when WireGuard is enabled.' -fi +mkdir -p "${WIREGUARD_STATE_DIR}" + +bashio::log.info "------------------------------" +bashio::log.info "Wireguard enabled, configuring" +bashio::log.info "------------------------------" if bashio::config.has_value 'wireguard_config'; then - configured_name="$(bashio::config 'wireguard_config')" - configured_name="${configured_name##*/}" - if [[ -z "${configured_name}" ]]; then + wireguard_config="$(bashio::config 'wireguard_config')" + wireguard_config="${wireguard_config##*/}" + if [[ -z "${wireguard_config}" ]]; then bashio::log.info 'wireguard_config option left empty. Attempting automatic selection.' - elif bashio::fs.file_exists "/config/wireguard/${configured_name}"; then - wireguard_config="/config/wireguard/${configured_name}" + mapfile -t configs < <(find /config/wireguard -maxdepth 1 -type f -name '*.conf' -print) + if [ "${#configs[@]}" -eq 0 ]; then + bashio::exit.nok 'WireGuard is enabled but no .conf file was found in /config/wireguard.' + elif [ "${#configs[@]}" -eq 1 ]; then + wireguard_config="${configs[0]}" + bashio::log.info "WireGuard configuration not specified. Using ${wireguard_config##*/}." + elif bashio::fs.file_exists '/config/wireguard/config.conf'; then + wireguard_config='/config/wireguard/config.conf' + bashio::log.info 'Using default WireGuard configuration config.conf.' + else + bashio::exit.nok "Multiple WireGuard configuration files detected. Please set the 'wireguard_config' option." + fi + elif bashio::fs.file_exists "/config/wireguard/${wireguard_config}"; then + wireguard_config="/config/wireguard/${wireguard_config}" else - bashio::exit.nok "WireGuard configuration '/config/wireguard/${configured_name}' not found." + bashio::exit.nok "WireGuard configuration '/config/wireguard/${wireguard_config}' not found." fi fi -if [ -z "${wireguard_config:-}" ]; then - mapfile -t configs < <(find /config/wireguard -maxdepth 1 -type f -name '*.conf' -print) - if [ "${#configs[@]}" -eq 0 ]; then - bashio::exit.nok 'WireGuard is enabled but no .conf file was found in /config/wireguard.' - elif [ "${#configs[@]}" -eq 1 ]; then - wireguard_config="${configs[0]}" - bashio::log.info "WireGuard configuration not specified. Using ${wireguard_config##*/}." - elif bashio::fs.file_exists '/config/wireguard/config.conf'; then - wireguard_config='/config/wireguard/config.conf' - bashio::log.info 'Using default WireGuard configuration config.conf.' - else - bashio::exit.nok "Multiple WireGuard configuration files detected. Please set the 'wireguard_config' option." - fi -fi - -dos2unix "${wireguard_config}" >/dev/null 2>&1 || true - interface_name="$(basename "${wireguard_config}" .conf)" if [[ -z "${interface_name}" ]]; then interface_name='wg0' @@ -60,12 +57,26 @@ fi wireguard_runtime_config="${WIREGUARD_STATE_DIR}/${interface_name}.conf" cp "${wireguard_config}" "${wireguard_runtime_config}" -chmod 600 "${wireguard_runtime_config}" 2>/dev/null || true +chmod 600 "${wireguard_runtime_config}" + +dos2unix "${wireguard_runtime_config}" >/dev/null 2>&1 || true +sed -i '/^[[:space:]]*[;#]/d' "${wireguard_runtime_config}" +sed -i 's/#.*//' "${wireguard_runtime_config}" +sed -i '/^[[:space:]]*$/d' "${wireguard_runtime_config}" +sed -i '/^[[:blank:]]*$/d' "${wireguard_runtime_config}" +sed -i '/DNS/d' "${wireguard_runtime_config}" +sed -i '/PostUp/d' "${wireguard_runtime_config}" +sed -i '/PostDown/d' "${wireguard_runtime_config}" +sed -i '/SaveConfig/d' "${wireguard_runtime_config}" +sed -i "\$q" "${wireguard_runtime_config}" + bashio::log.info 'Prepared WireGuard runtime configuration for initial connection attempt.' echo "${wireguard_runtime_config}" > "${WIREGUARD_STATE_DIR}/config" echo "${interface_name}" > "${WIREGUARD_STATE_DIR}/interface" +bashio::log.info "Using interface binding in the qBittorrent app" + if bashio::fs.file_exists "${QBT_CONFIG_FILE}"; then sed -i '/Interface/d' "${QBT_CONFIG_FILE}" sed -i "/\\[Preferences\\]/ i\\Connection\\\\Interface=${interface_name}" "${QBT_CONFIG_FILE}" From a82cb736340ce6b6cdddb0280f4b08146320f2de Mon Sep 17 00:00:00 2001 From: Alexandre <44178713+alexbelgium@users.noreply.github.com> Date: Wed, 28 Jan 2026 13:56:39 +0100 Subject: [PATCH 09/35] No more bin folder --- qbittorrent/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qbittorrent/Dockerfile b/qbittorrent/Dockerfile index bb51a4b1d..feb423913 100644 --- a/qbittorrent/Dockerfile +++ b/qbittorrent/Dockerfile @@ -126,7 +126,7 @@ RUN chmod 744 /ha_autoapps.sh && /ha_autoapps.sh "$PACKAGES" && rm /ha_autoapps. # 4 Entrypoint # ################ -RUN chmod +x /usr/local/bin/* /usr/local/sbin/* +RUN chmod +x /usr/local/sbin/* # Add entrypoint ENV S6_STAGE2_HOOK=/ha_entrypoint.sh From 10b743e6fdcfc78838fa182f54b474fa3b9369d6 Mon Sep 17 00:00:00 2001 From: Alexandre <44178713+alexbelgium@users.noreply.github.com> Date: Wed, 28 Jan 2026 14:15:11 +0100 Subject: [PATCH 10/35] Avoid wireguard conf validation message --- qbittorrent/config.yaml | 3 --- 1 file changed, 3 deletions(-) diff --git a/qbittorrent/config.yaml b/qbittorrent/config.yaml index d6be10613..63d3ebf5b 100644 --- a/qbittorrent/config.yaml +++ b/qbittorrent/config.yaml @@ -88,10 +88,7 @@ options: certfile: fullchain.pem customUI: vuetorrent keyfile: privkey.pem - qbit_manage: false ssl: false - wireguard_enabled: false - wireguard_config: "" whitelist: localhost,127.0.0.1,172.30.0.0/16,192.168.0.0/16 panel_admin: false panel_icon: mdi:progress-download From ffaec60c47d2d1154d136ddb957255b8f2c15b88 Mon Sep 17 00:00:00 2001 From: litinoveweedle <15144712+litinoveweedle@users.noreply.github.com> Date: Thu, 29 Jan 2026 16:51:57 +0100 Subject: [PATCH 11/35] correct check for bashio fs.directory_exists --- qbittorrent/rootfs/etc/cont-init.d/93-openvpn.sh | 2 +- qbittorrent/rootfs/etc/cont-init.d/94-wireguard.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/qbittorrent/rootfs/etc/cont-init.d/93-openvpn.sh b/qbittorrent/rootfs/etc/cont-init.d/93-openvpn.sh index 787d3c6b0..590852c0d 100755 --- a/qbittorrent/rootfs/etc/cont-init.d/93-openvpn.sh +++ b/qbittorrent/rootfs/etc/cont-init.d/93-openvpn.sh @@ -10,7 +10,7 @@ declare interface_name="" declare openvpn_username declare openvpn_password -if bashio::file_exists "${OPENVPN_STATE_DIR}"; then +if bashio::fs.directory_exists "${OPENVPN_STATE_DIR}"; then bashio::log.warning "Previous OpenVPN state directory found, cleaning up." rm -Rf "${OPENVPN_STATE_DIR}" fi diff --git a/qbittorrent/rootfs/etc/cont-init.d/94-wireguard.sh b/qbittorrent/rootfs/etc/cont-init.d/94-wireguard.sh index dcec326de..1c7057fa0 100755 --- a/qbittorrent/rootfs/etc/cont-init.d/94-wireguard.sh +++ b/qbittorrent/rootfs/etc/cont-init.d/94-wireguard.sh @@ -8,7 +8,7 @@ declare wireguard_config="" declare wireguard_runtime_config="" declare interface_name="" -if bashio::file_exists "${WIREGUARD_STATE_DIR}"; then +if bashio::fs.directory_exists "${WIREGUARD_STATE_DIR}"; then bashio::log.warning "Previous WireGuard state directory found, cleaning up." rm -Rf "${WIREGUARD_STATE_DIR}" fi From 092bf0ab4676a381558fd7515f5453d59ea8ea17 Mon Sep 17 00:00:00 2001 From: litinoveweedle <15144712+litinoveweedle@users.noreply.github.com> Date: Thu, 29 Jan 2026 17:04:31 +0100 Subject: [PATCH 12/35] fixed find of openvpn configs --- qbittorrent/rootfs/etc/cont-init.d/93-openvpn.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qbittorrent/rootfs/etc/cont-init.d/93-openvpn.sh b/qbittorrent/rootfs/etc/cont-init.d/93-openvpn.sh index 590852c0d..d63a5aa6b 100755 --- a/qbittorrent/rootfs/etc/cont-init.d/93-openvpn.sh +++ b/qbittorrent/rootfs/etc/cont-init.d/93-openvpn.sh @@ -47,7 +47,7 @@ if bashio::config.has_value 'openvpn_config'; then openvpn_config="${openvpn_config##*/}" if [[ -z "${openvpn_config}" ]]; then bashio::log.info 'openvpn_config option left empty. Attempting automatic selection.' - mapfile -t configs < <(find /config/openvpn -maxdepth 1 -type f -name '*.conf' -o -name '*.ovpn' -print) + mapfile -t configs < <(find /config/openvpn -maxdepth 1 \( -type f -name '*.conf' -o -name '*.ovpn' \) -print) if [ "${#configs[@]}" -eq 0 ]; then bashio::exit.nok 'OpenVPN is enabled but no .conf or .ovpn file was found in /config/openvpn.' elif [ "${#configs[@]}" -eq 1 ]; then From 38302a84286cefc3deedb825a087214bd71438f2 Mon Sep 17 00:00:00 2001 From: litinoveweedle <15144712+litinoveweedle@users.noreply.github.com> Date: Thu, 29 Jan 2026 17:08:40 +0100 Subject: [PATCH 13/35] fix opevpn config path in log message --- qbittorrent/rootfs/etc/cont-init.d/93-openvpn.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qbittorrent/rootfs/etc/cont-init.d/93-openvpn.sh b/qbittorrent/rootfs/etc/cont-init.d/93-openvpn.sh index d63a5aa6b..9d153a8c6 100755 --- a/qbittorrent/rootfs/etc/cont-init.d/93-openvpn.sh +++ b/qbittorrent/rootfs/etc/cont-init.d/93-openvpn.sh @@ -68,7 +68,7 @@ fi interface_name="$(sed -n "/^dev tun/p" "${openvpn_config}" | awk -F' ' '{print $2}')" if [[ -z "${interface_name}" ]]; then - bashio::exit.nok "OpenVPN configuration '/config/openvpn/${openvpn_config}' miss device directive." + bashio::exit.nok "OpenVPN configuration '${openvpn_config}' misses device directive." elif [[ ${interface_name} = "tun" ]]; then interface_name='tun0' elif [[ ${interface_name} = "tap" ]]; then From bfdbd1e2898befebecc9b6908b1f05b92ade868a Mon Sep 17 00:00:00 2001 From: litinoveweedle <15144712+litinoveweedle@users.noreply.github.com> Date: Thu, 29 Jan 2026 17:18:20 +0100 Subject: [PATCH 14/35] fixed vpn script execute permissions --- qbittorrent/rootfs/usr/local/sbin/vpn | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 qbittorrent/rootfs/usr/local/sbin/vpn diff --git a/qbittorrent/rootfs/usr/local/sbin/vpn b/qbittorrent/rootfs/usr/local/sbin/vpn old mode 100644 new mode 100755 From 699400ad401b6fb50065e4826ec9a90a23b405b7 Mon Sep 17 00:00:00 2001 From: litinoveweedle <15144712+litinoveweedle@users.noreply.github.com> Date: Thu, 29 Jan 2026 17:21:34 +0100 Subject: [PATCH 15/35] the real fix to fix vpn script permissions ;-) --- qbittorrent/Dockerfile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/qbittorrent/Dockerfile b/qbittorrent/Dockerfile index feb423913..419e622da 100644 --- a/qbittorrent/Dockerfile +++ b/qbittorrent/Dockerfile @@ -126,8 +126,6 @@ RUN chmod 744 /ha_autoapps.sh && /ha_autoapps.sh "$PACKAGES" && rm /ha_autoapps. # 4 Entrypoint # ################ -RUN chmod +x /usr/local/sbin/* - # Add entrypoint ENV S6_STAGE2_HOOK=/ha_entrypoint.sh ADD "https://raw.githubusercontent.com/alexbelgium/hassio-addons/master/.templates/ha_entrypoint.sh" "/ha_entrypoint.sh" @@ -135,6 +133,7 @@ RUN chmod 777 /ha_entrypoint.sh COPY rootfs/ / RUN find . -type f \( -name "*.sh" -o -name "run" \) -print -exec chmod +x {} \; +RUN chmod +x /usr/local/sbin/* ADD "https://raw.githubusercontent.com/alexbelgium/hassio-addons/master/.templates/bashio-standalone.sh" "/.bashio-standalone.sh" RUN chmod 777 /.bashio-standalone.sh From 2e0327fe87717c6b9f4b2787ee53ead0335fbaf3 Mon Sep 17 00:00:00 2001 From: litinoveweedle <15144712+litinoveweedle@users.noreply.github.com> Date: Tue, 3 Feb 2026 13:00:32 +0100 Subject: [PATCH 16/35] fixes replaced IFS by mapfile where relevant --- qbittorrent/rootfs/usr/local/sbin/vpn | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/qbittorrent/rootfs/usr/local/sbin/vpn b/qbittorrent/rootfs/usr/local/sbin/vpn index 8bc0ce64e..f6c6c9736 100755 --- a/qbittorrent/rootfs/usr/local/sbin/vpn +++ b/qbittorrent/rootfs/usr/local/sbin/vpn @@ -32,7 +32,7 @@ _parse_dns() { local -a dns_backup_ipv4=("8.8.8.8" "1.1.1.1") local -a dns_backup_ipv6=("2001:4860:4860::8888" "2606:4700:4700::1111") - IFS=',' read -ra dns_conf <<< $(bashio::config 'DNS_server' | tr -d ' ') + mapfile -d ',' -t dns_conf < <(bashio::config 'DNS_server' | tr -d ' ') if [ ${config["IPv4Enabled"]} = "true" ]; then for dns_ip in "${dns_conf[@]}"; do _is_ip_address "${dns_ip}" @@ -110,7 +110,9 @@ _resolvconf() { _resolve_hostname() { local hostname=$1 - local ips="" + local -a ips="" + local -a ipv4_candidates=() + local -a ipv6_candidates=() # Resolve hostname to IPv6 mapfile -t ipv6_candidates < <(getent ahostsv6 "${hostname}" | awk '{print $1}' | uniq) @@ -120,15 +122,21 @@ _resolve_hostname() { if [ ${#ipv6_candidates[@]} -gt 0 ]; then bashio::log.debug "Resolved ${hostname} to ${ipv6_candidates[@]}" - ips=${ipv6_candidates[@]} + ips+=("${ipv6_candidates[@]}") fi if [ ${#ipv4_candidates[@]} -gt 0 ]; then bashio::log.debug "Resolved ${hostname} to ${ipv4_candidates[@]}" - ips="${ips} ${ipv4_candidates[@]}" + ips+=("${ipv4_candidates[@]}") fi - return $ips + echo "${ips[@]}" + + if [ ${#ips[@]} -gt 0 ]; then + return 0 + else + return 1 + fi } _routing_add() { @@ -195,7 +203,9 @@ _wireguard_up() { _cmd "ip link add ${config["Interface"]} type wireguard" || return 1 local allowed_ips="" - for local_ip in ${config["Address"]}; do + local -a local_ips=() + mapfile -d ',' -t local_ips < <(echo "${config["Address"]}" | tr -d ' ') + for local_ip in ${local_ips[@]}; do _is_ip_address "${local_ip}" local is_ip=$? if [ "${is_ip}" -eq 1 ]; then @@ -277,7 +287,8 @@ wireguard() { if [ "${mode}" = "up" ]; then bashio::log.info "Starting WireGuard on interface ${config["Interface"]}..." if _is_ip_address ${config["EndpointHost"]}; then - local endpoint_ips=$(_resolve_hostname ${config["EndpointHost"]}) + local -a endpoint_ips=() + mapfile -d ' ' -t endpoint_ips < <(_resolve_hostname ${config["EndpointHost"]}) if [ ${#endpoint_ips[@]} -eq 0 ]; then bashio::log.error "Failed to resolve WireGuard endpoint hostname: ${config["EndpointHost"]}" bashio::exit.nok 'WireGuard start failed.' From 333116c8fcc69887220184755419c589ef033b13 Mon Sep 17 00:00:00 2001 From: litinoveweedle <15144712+litinoveweedle@users.noreply.github.com> Date: Tue, 3 Feb 2026 17:34:48 +0100 Subject: [PATCH 17/35] improve endpoint hostname validation --- qbittorrent/rootfs/usr/local/sbin/vpn | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/qbittorrent/rootfs/usr/local/sbin/vpn b/qbittorrent/rootfs/usr/local/sbin/vpn index f6c6c9736..af3db2035 100755 --- a/qbittorrent/rootfs/usr/local/sbin/vpn +++ b/qbittorrent/rootfs/usr/local/sbin/vpn @@ -72,8 +72,10 @@ _is_ip_address() { return 1 # IPv4 elif [ "$1" != "${1#*:[0-9a-fA-F]}" ]; then return 2 # IPv6 + elif host "$1" >/dev/null 2>&1; then + return 3 # resolvable hostname else - return 0 # Not an IP address + return 0 # neither IP nor resolvable hostname fi } @@ -286,7 +288,12 @@ wireguard() { if [ "${mode}" = "up" ]; then bashio::log.info "Starting WireGuard on interface ${config["Interface"]}..." - if _is_ip_address ${config["EndpointHost"]}; then + _is_ip_address ${config["EndpointHost"]} + local is_ip=$? + if [ "$is_ip" -eq 0 ]; then + bashio::log.error "WireGuard endpoint ${config["EndpointHost"]} is neither a valid IP address nor a resolvable hostname." + bashio::exit.nok 'WireGuard start failed.' + elif [ "$is_ip" -eq 3 ]; then local -a endpoint_ips=() mapfile -d ' ' -t endpoint_ips < <(_resolve_hostname ${config["EndpointHost"]}) if [ ${#endpoint_ips[@]} -eq 0 ]; then From 9789b9dab62db34cc0595bbdb9649264d382ae4d Mon Sep 17 00:00:00 2001 From: litinoveweedle <15144712+litinoveweedle@users.noreply.github.com> Date: Tue, 3 Feb 2026 17:42:40 +0100 Subject: [PATCH 18/35] added comments --- qbittorrent/rootfs/usr/local/sbin/vpn | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/qbittorrent/rootfs/usr/local/sbin/vpn b/qbittorrent/rootfs/usr/local/sbin/vpn index af3db2035..7edb0eb05 100755 --- a/qbittorrent/rootfs/usr/local/sbin/vpn +++ b/qbittorrent/rootfs/usr/local/sbin/vpn @@ -1,7 +1,7 @@ #!/usr/bin/with-contenv bashio # shellcheck shell=bash -# --- WireGuard Specific Logic --- +# --- Common Functions --- declare -A config config["MySelf"]="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")" @@ -190,6 +190,8 @@ _routing_del() { while _cmd "ip -6 route del default dev ${config["Interface"]} table ${config["Table"]} 2>/dev/null"; do :; done } +# --- WireGuard Specific Logic --- + _wireguard_up() { bashio::log.warning "This script force Wireguard to ignore any routes and DNS settings." bashio::log.warning "Default route will be inserted into custom routing table: ${config["Table"]}" @@ -333,6 +335,8 @@ wireguard() { bashio::exit.nok 'WireGuard start failed.' } +# --- OpenVPN Specific Logic --- + _openvpn_up() { bashio::log.warning "This script force OpenvPN to ignore any routes and DNS settings pushed by the server." bashio::log.warning "Default route will be inserted into custom routing table: ${config["Table"]}" @@ -421,6 +425,7 @@ openvpn() { bashio::log.info "Starting OpenVPN with configuration file: ${config_file}" } +# --- Entry Point --- if [ $# -ne 2 ]; then bashio::log.error "Invalid number of arguments. Usage: vpn.sh " From 8c5171b02732e63f295dee773b84559a1b04b831 Mon Sep 17 00:00:00 2001 From: litinoveweedle <15144712+litinoveweedle@users.noreply.github.com> Date: Tue, 3 Feb 2026 21:09:17 +0100 Subject: [PATCH 19/35] removed host dependency --- qbittorrent/rootfs/usr/local/sbin/vpn | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qbittorrent/rootfs/usr/local/sbin/vpn b/qbittorrent/rootfs/usr/local/sbin/vpn index 7edb0eb05..c16e39b5e 100755 --- a/qbittorrent/rootfs/usr/local/sbin/vpn +++ b/qbittorrent/rootfs/usr/local/sbin/vpn @@ -72,7 +72,7 @@ _is_ip_address() { return 1 # IPv4 elif [ "$1" != "${1#*:[0-9a-fA-F]}" ]; then return 2 # IPv6 - elif host "$1" >/dev/null 2>&1; then + elif getent ahosts "$1" >/dev/null 2>&1; then return 3 # resolvable hostname else return 0 # neither IP nor resolvable hostname From c69440da1a976496572eb868a1e4758385ccd07d Mon Sep 17 00:00:00 2001 From: Alexandre <44178713+alexbelgium@users.noreply.github.com> Date: Tue, 3 Feb 2026 21:31:19 +0100 Subject: [PATCH 20/35] Fix sed command for OpenVPN runtime config --- qbittorrent/rootfs/etc/cont-init.d/93-openvpn.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qbittorrent/rootfs/etc/cont-init.d/93-openvpn.sh b/qbittorrent/rootfs/etc/cont-init.d/93-openvpn.sh index 9d153a8c6..55d0e5ef6 100755 --- a/qbittorrent/rootfs/etc/cont-init.d/93-openvpn.sh +++ b/qbittorrent/rootfs/etc/cont-init.d/93-openvpn.sh @@ -91,7 +91,7 @@ sed -i '/^route/d' "${openvpn_runtime_config}" sed -i '/^auth-user-pass /d' "${openvpn_runtime_config}" sed -i '/^cd /d' "${openvpn_runtime_config}" sed -i '/^chroot /d' "${openvpn_runtime_config}" -sed -i '\$q' "${openvpn_runtime_config}" +sed -i '$q' "${openvpn_runtime_config}" bashio::log.info 'Prepared OpenVPN runtime configuration for initial connection attempt.' From 60cb4c9c26caa0ca821e04486599ec9f6a2010c7 Mon Sep 17 00:00:00 2001 From: Alexandre <44178713+alexbelgium@users.noreply.github.com> Date: Tue, 3 Feb 2026 21:59:11 +0100 Subject: [PATCH 21/35] Update vpn --- qbittorrent/rootfs/usr/local/sbin/vpn | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qbittorrent/rootfs/usr/local/sbin/vpn b/qbittorrent/rootfs/usr/local/sbin/vpn index c16e39b5e..0bad245d6 100755 --- a/qbittorrent/rootfs/usr/local/sbin/vpn +++ b/qbittorrent/rootfs/usr/local/sbin/vpn @@ -352,7 +352,7 @@ _openvpn_up() { chmod 755 ${config["PostDownScript"]} # Start OpenVPN in the background - _cmd "/usr/sbin/openvpn + _cmd "/usr/sbin/openvpn \ --config "${config["ConfigFile"]}" \ --script-security 2 --daemon --log /dev/null \ --auth-user-pass "${OPENVPN_STATE_DIR}/credentials.conf" \ From 5647aac5289a8e7b38664050b72b9f0f4c11fe4c Mon Sep 17 00:00:00 2001 From: litinoveweedle <15144712+litinoveweedle@users.noreply.github.com> Date: Tue, 3 Feb 2026 22:36:46 +0100 Subject: [PATCH 22/35] multiple bug fixes fix script premature termination as bashio set -e? fix parsing of the DNS servers improve ip/host checks --- qbittorrent/Dockerfile | 2 +- qbittorrent/rootfs/usr/local/sbin/vpn | 45 ++++++++++++--------------- 2 files changed, 21 insertions(+), 26 deletions(-) diff --git a/qbittorrent/Dockerfile b/qbittorrent/Dockerfile index 419e622da..c10b2ba43 100644 --- a/qbittorrent/Dockerfile +++ b/qbittorrent/Dockerfile @@ -112,7 +112,7 @@ RUN chmod 744 /ha_automodules.sh && /ha_automodules.sh "$MODULES" && rm /ha_auto # && chmod a+x /etc/s6-overlay/s6-rc.d/$SCRIPTSNAME/* ; done; fi # Manual apps -ARG PACKAGES="wireguard-tools iptables ip6tables iptables-legacy" +ARG PACKAGES="ipcalc wireguard-tools iptables ip6tables iptables-legacy" # Automatic apps & bashio ADD "https://raw.githubusercontent.com/alexbelgium/hassio-addons/master/.templates/ha_autoapps.sh" "/ha_autoapps.sh" diff --git a/qbittorrent/rootfs/usr/local/sbin/vpn b/qbittorrent/rootfs/usr/local/sbin/vpn index c16e39b5e..43ce0ac19 100755 --- a/qbittorrent/rootfs/usr/local/sbin/vpn +++ b/qbittorrent/rootfs/usr/local/sbin/vpn @@ -31,13 +31,14 @@ _parse_dns() { local -a dns_conf=() local -a dns_backup_ipv4=("8.8.8.8" "1.1.1.1") local -a dns_backup_ipv6=("2001:4860:4860::8888" "2606:4700:4700::1111") + local dns_servers=$(bashio::config 'DNS_server') - mapfile -d ',' -t dns_conf < <(bashio::config 'DNS_server' | tr -d ' ') + mapfile -d ',' -t dns_conf < <(echo "${dns_servers}" | tr -d ' ') if [ ${config["IPv4Enabled"]} = "true" ]; then for dns_ip in "${dns_conf[@]}"; do - _is_ip_address "${dns_ip}" - local is_ip=$? - if [ "${is_ip}" -eq 1 ]; then + local result=0 + _check_host "${dns_ip}" || result=$? + if [ "${result}" -eq 1 ]; then dns_servers_ipv4+=("${dns_ip}") fi done @@ -48,9 +49,9 @@ _parse_dns() { fi if [ ${config["IPv6Enabled"]} = "true" ]; then for dns_ip in "${dns_conf[@]}"; do - _is_ip_address "${dns_ip}" - local is_ip=$? - if [ "${is_ip}" -eq 2 ]; then + local result=0 + _check_host "${dns_ip}" || result=$? + if [ "${result}" -eq 2 ]; then dns_servers_ipv6+=("${dns_ip}") fi done @@ -67,13 +68,13 @@ _cmd() { eval "${cmd}" } -_is_ip_address() { - if [ "$1" != "${1#*[0-9].[0-9]}" ]; then +_check_host() { + if ipcalc -c -4 "$1" >/dev/null 2>&1; then return 1 # IPv4 - elif [ "$1" != "${1#*:[0-9a-fA-F]}" ]; then + elif ipcalc -c -6 "$1" >/dev/null 2>&1; then return 2 # IPv6 elif getent ahosts "$1" >/dev/null 2>&1; then - return 3 # resolvable hostname + return 3 # resolvable hostnamee else return 0 # neither IP nor resolvable hostname fi @@ -133,12 +134,6 @@ _resolve_hostname() { fi echo "${ips[@]}" - - if [ ${#ips[@]} -gt 0 ]; then - return 0 - else - return 1 - fi } _routing_add() { @@ -210,12 +205,12 @@ _wireguard_up() { local -a local_ips=() mapfile -d ',' -t local_ips < <(echo "${config["Address"]}" | tr -d ' ') for local_ip in ${local_ips[@]}; do - _is_ip_address "${local_ip}" - local is_ip=$? - if [ "${is_ip}" -eq 1 ]; then + local result=0 + _check_host "${local_ip}" || result=$? + if [ "${result}" -eq 1 ]; then allowed_ips="${allowed_ips},0.0.0.0/0" _cmd "ip addr add ${local_ip} dev ${config["Interface"]}" || return 1 - elif [ "${is_ip}" -eq 2 ]; then + elif [ "${result}" -eq 2 ]; then allowed_ips="${allowed_ips},::/0" _cmd "ip addr add ${local_ip} dev ${config["Interface"]}" || return 1 else @@ -290,12 +285,12 @@ wireguard() { if [ "${mode}" = "up" ]; then bashio::log.info "Starting WireGuard on interface ${config["Interface"]}..." - _is_ip_address ${config["EndpointHost"]} - local is_ip=$? - if [ "$is_ip" -eq 0 ]; then + local result=0 + _check_host ${config["EndpointHost"]} || result=$? + if [ "${result}" -eq 0 ]; then bashio::log.error "WireGuard endpoint ${config["EndpointHost"]} is neither a valid IP address nor a resolvable hostname." bashio::exit.nok 'WireGuard start failed.' - elif [ "$is_ip" -eq 3 ]; then + elif [ "${result}" -eq 3 ]; then local -a endpoint_ips=() mapfile -d ' ' -t endpoint_ips < <(_resolve_hostname ${config["EndpointHost"]}) if [ ${#endpoint_ips[@]} -eq 0 ]; then From ec4b5417ea3cfa147bd2e1dd0a56c968e7ad973a Mon Sep 17 00:00:00 2001 From: litinoveweedle <15144712+litinoveweedle@users.noreply.github.com> Date: Tue, 3 Feb 2026 22:58:12 +0100 Subject: [PATCH 23/35] fix DNS_server parsing --- qbittorrent/rootfs/usr/local/sbin/vpn | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qbittorrent/rootfs/usr/local/sbin/vpn b/qbittorrent/rootfs/usr/local/sbin/vpn index 02a2718be..72692c07f 100755 --- a/qbittorrent/rootfs/usr/local/sbin/vpn +++ b/qbittorrent/rootfs/usr/local/sbin/vpn @@ -33,7 +33,7 @@ _parse_dns() { local -a dns_backup_ipv6=("2001:4860:4860::8888" "2606:4700:4700::1111") local dns_servers=$(bashio::config 'DNS_server') - mapfile -d ',' -t dns_conf < <(echo "${dns_servers}" | tr -d ' ') + mapfile -d ',' -t dns_conf < <(echo "${dns_servers}" | tr -d ' ' | tr -d '\n') if [ ${config["IPv4Enabled"]} = "true" ]; then for dns_ip in "${dns_conf[@]}"; do local result=0 From 81287577a2c1dba4f6ad41d9e1309472a236c717 Mon Sep 17 00:00:00 2001 From: litinoveweedle <15144712+litinoveweedle@users.noreply.github.com> Date: Tue, 3 Feb 2026 23:05:57 +0100 Subject: [PATCH 24/35] remove hopefully unnecessary image packages --- qbittorrent/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qbittorrent/Dockerfile b/qbittorrent/Dockerfile index a324788fb..8f58be02c 100644 --- a/qbittorrent/Dockerfile +++ b/qbittorrent/Dockerfile @@ -112,7 +112,7 @@ RUN chmod 744 /ha_automodules.sh && /ha_automodules.sh "$MODULES" && rm /ha_auto # && chmod a+x /etc/s6-overlay/s6-rc.d/$SCRIPTSNAME/* ; done; fi # Manual apps -ARG PACKAGES="ipcalc wireguard-tools iptables ip6tables iptables-legacy" +ARG PACKAGES="ipcalc wireguard-tools" # Automatic apps & bashio ADD "https://raw.githubusercontent.com/alexbelgium/hassio-addons/master/.templates/ha_autoapps.sh" "/ha_autoapps.sh" From 5daf3c240f0f7b98bf8be225343361388bd8560e Mon Sep 17 00:00:00 2001 From: litinoveweedle <15144712+litinoveweedle@users.noreply.github.com> Date: Tue, 3 Feb 2026 23:16:56 +0100 Subject: [PATCH 25/35] config selection logic fix --- .../rootfs/etc/cont-init.d/93-openvpn.sh | 38 +++++++++---------- .../rootfs/etc/cont-init.d/94-wireguard.sh | 38 +++++++++---------- 2 files changed, 36 insertions(+), 40 deletions(-) diff --git a/qbittorrent/rootfs/etc/cont-init.d/93-openvpn.sh b/qbittorrent/rootfs/etc/cont-init.d/93-openvpn.sh index 55d0e5ef6..0d0839082 100755 --- a/qbittorrent/rootfs/etc/cont-init.d/93-openvpn.sh +++ b/qbittorrent/rootfs/etc/cont-init.d/93-openvpn.sh @@ -42,28 +42,26 @@ fi echo -e "${openvpn_username}\n${openvpn_password}" > "${OPENVPN_STATE_DIR}/credentials.conf" chmod 600 "${OPENVPN_STATE_DIR}/credentials.conf" -if bashio::config.has_value 'openvpn_config'; then - openvpn_config="$(bashio::config 'openvpn_config')" - openvpn_config="${openvpn_config##*/}" - if [[ -z "${openvpn_config}" ]]; then - bashio::log.info 'openvpn_config option left empty. Attempting automatic selection.' - mapfile -t configs < <(find /config/openvpn -maxdepth 1 \( -type f -name '*.conf' -o -name '*.ovpn' \) -print) - if [ "${#configs[@]}" -eq 0 ]; then - bashio::exit.nok 'OpenVPN is enabled but no .conf or .ovpn file was found in /config/openvpn.' - elif [ "${#configs[@]}" -eq 1 ]; then - openvpn_config="${configs[0]}" - bashio::log.info "OpenVPN configuration not specified. Using ${openvpn_config##*/}." - elif bashio::fs.file_exists '/config/openvpn/config.conf'; then - openvpn_config='/config/openvpn/config.conf' - bashio::log.info 'Using default OpenVPN configuration config.conf.' - else - bashio::exit.nok "Multiple OpenVPN configuration files detected. Please set the 'openvpn_config' option." - fi - elif bashio::fs.file_exists "/config/openvpn/${openvpn_config}"; then - openvpn_config="/config/openvpn/${openvpn_config}" +openvpn_config="$(bashio::config 'openvpn_config')" +openvpn_config="${openvpn_config##*/}" +if [[ -z "${openvpn_config}" ]]; then + bashio::log.info 'openvpn_config option left empty. Attempting automatic selection.' + mapfile -t configs < <(find /config/openvpn -maxdepth 1 \( -type f -name '*.conf' -o -name '*.ovpn' \) -print) + if [ "${#configs[@]}" -eq 0 ]; then + bashio::exit.nok 'OpenVPN is enabled but no .conf or .ovpn file was found in /config/openvpn.' + elif [ "${#configs[@]}" -eq 1 ]; then + openvpn_config="${configs[0]}" + bashio::log.info "OpenVPN configuration not specified. Using ${openvpn_config##*/}." + elif bashio::fs.file_exists '/config/openvpn/config.conf'; then + openvpn_config='/config/openvpn/config.conf' + bashio::log.info 'Using default OpenVPN configuration config.conf.' else - bashio::exit.nok "OpenVPN configuration '/config/openvpn/${openvpn_config}' not found." + bashio::exit.nok "Multiple OpenVPN configuration files detected. Please set the 'openvpn_config' option." fi +elif bashio::fs.file_exists "/config/openvpn/${openvpn_config}"; then + openvpn_config="/config/openvpn/${openvpn_config}" +else + bashio::exit.nok "OpenVPN configuration '/config/openvpn/${openvpn_config}' not found." fi interface_name="$(sed -n "/^dev tun/p" "${openvpn_config}" | awk -F' ' '{print $2}')" diff --git a/qbittorrent/rootfs/etc/cont-init.d/94-wireguard.sh b/qbittorrent/rootfs/etc/cont-init.d/94-wireguard.sh index 1c7057fa0..fe8eddf2a 100755 --- a/qbittorrent/rootfs/etc/cont-init.d/94-wireguard.sh +++ b/qbittorrent/rootfs/etc/cont-init.d/94-wireguard.sh @@ -25,28 +25,26 @@ bashio::log.info "------------------------------" bashio::log.info "Wireguard enabled, configuring" bashio::log.info "------------------------------" -if bashio::config.has_value 'wireguard_config'; then - wireguard_config="$(bashio::config 'wireguard_config')" - wireguard_config="${wireguard_config##*/}" - if [[ -z "${wireguard_config}" ]]; then - bashio::log.info 'wireguard_config option left empty. Attempting automatic selection.' - mapfile -t configs < <(find /config/wireguard -maxdepth 1 -type f -name '*.conf' -print) - if [ "${#configs[@]}" -eq 0 ]; then - bashio::exit.nok 'WireGuard is enabled but no .conf file was found in /config/wireguard.' - elif [ "${#configs[@]}" -eq 1 ]; then - wireguard_config="${configs[0]}" - bashio::log.info "WireGuard configuration not specified. Using ${wireguard_config##*/}." - elif bashio::fs.file_exists '/config/wireguard/config.conf'; then - wireguard_config='/config/wireguard/config.conf' - bashio::log.info 'Using default WireGuard configuration config.conf.' - else - bashio::exit.nok "Multiple WireGuard configuration files detected. Please set the 'wireguard_config' option." - fi - elif bashio::fs.file_exists "/config/wireguard/${wireguard_config}"; then - wireguard_config="/config/wireguard/${wireguard_config}" +wireguard_config="$(bashio::config 'wireguard_config')" +wireguard_config="${wireguard_config##*/}" +if [[ -z "${wireguard_config}" ]]; then + bashio::log.info 'wireguard_config option left empty. Attempting automatic selection.' + mapfile -t configs < <(find /config/wireguard -maxdepth 1 -type f -name '*.conf' -print) + if [ "${#configs[@]}" -eq 0 ]; then + bashio::exit.nok 'WireGuard is enabled but no .conf file was found in /config/wireguard.' + elif [ "${#configs[@]}" -eq 1 ]; then + wireguard_config="${configs[0]}" + bashio::log.info "WireGuard configuration not specified. Using ${wireguard_config##*/}." + elif bashio::fs.file_exists '/config/wireguard/config.conf'; then + wireguard_config='/config/wireguard/config.conf' + bashio::log.info 'Using default WireGuard configuration config.conf.' else - bashio::exit.nok "WireGuard configuration '/config/wireguard/${wireguard_config}' not found." + bashio::exit.nok "Multiple WireGuard configuration files detected. Please set the 'wireguard_config' option." fi +elif bashio::fs.file_exists "/config/wireguard/${wireguard_config}"; then + wireguard_config="/config/wireguard/${wireguard_config}" +else + bashio::exit.nok "WireGuard configuration '/config/wireguard/${wireguard_config}' not found." fi interface_name="$(basename "${wireguard_config}" .conf)" From 9cb488324076373506ab7b712c2f9bccaf4b659e Mon Sep 17 00:00:00 2001 From: Alexandre <44178713+alexbelgium@users.noreply.github.com> Date: Wed, 4 Feb 2026 08:56:17 +0100 Subject: [PATCH 26/35] Add execute permission to vpn script --- qbittorrent/Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/qbittorrent/Dockerfile b/qbittorrent/Dockerfile index 8f58be02c..6f7fc4b13 100644 --- a/qbittorrent/Dockerfile +++ b/qbittorrent/Dockerfile @@ -92,6 +92,7 @@ RUN \ # Copy local files COPY rootfs/ / RUN find /etc -type f \( -name "*.sh" -o -name "run" -o -name "finish" \) -exec chmod +x {} + +RUN chmod +x /usr/local/sbin/vpn # Uses /bin for compatibility purposes # hadolint ignore=DL4005 From b04ac63ce74fe1563f029fde5ffecf7f716af046 Mon Sep 17 00:00:00 2001 From: Alexandre <44178713+alexbelgium@users.noreply.github.com> Date: Wed, 4 Feb 2026 09:19:26 +0100 Subject: [PATCH 27/35] Check for openvpn_config option to avoid assigning string "null" to variable --- qbittorrent/rootfs/etc/cont-init.d/93-openvpn.sh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/qbittorrent/rootfs/etc/cont-init.d/93-openvpn.sh b/qbittorrent/rootfs/etc/cont-init.d/93-openvpn.sh index 0d0839082..b82afb701 100755 --- a/qbittorrent/rootfs/etc/cont-init.d/93-openvpn.sh +++ b/qbittorrent/rootfs/etc/cont-init.d/93-openvpn.sh @@ -42,8 +42,10 @@ fi echo -e "${openvpn_username}\n${openvpn_password}" > "${OPENVPN_STATE_DIR}/credentials.conf" chmod 600 "${OPENVPN_STATE_DIR}/credentials.conf" -openvpn_config="$(bashio::config 'openvpn_config')" -openvpn_config="${openvpn_config##*/}" +if bashio::config.has_value "openvpn_config"; then + openvpn_config="$(bashio::config 'openvpn_config')" + openvpn_config="${openvpn_config##*/}" +fi if [[ -z "${openvpn_config}" ]]; then bashio::log.info 'openvpn_config option left empty. Attempting automatic selection.' mapfile -t configs < <(find /config/openvpn -maxdepth 1 \( -type f -name '*.conf' -o -name '*.ovpn' \) -print) From 76a6a6496564ea9b412422b8c204c2f51384be49 Mon Sep 17 00:00:00 2001 From: litinoveweedle <15144712+litinoveweedle@users.noreply.github.com> Date: Wed, 4 Feb 2026 09:26:48 +0100 Subject: [PATCH 28/35] Check for wireguard_config option to avoid assigning string "null" to variable --- qbittorrent/rootfs/etc/cont-init.d/94-wireguard.sh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/qbittorrent/rootfs/etc/cont-init.d/94-wireguard.sh b/qbittorrent/rootfs/etc/cont-init.d/94-wireguard.sh index fe8eddf2a..3afa12619 100755 --- a/qbittorrent/rootfs/etc/cont-init.d/94-wireguard.sh +++ b/qbittorrent/rootfs/etc/cont-init.d/94-wireguard.sh @@ -25,8 +25,10 @@ bashio::log.info "------------------------------" bashio::log.info "Wireguard enabled, configuring" bashio::log.info "------------------------------" -wireguard_config="$(bashio::config 'wireguard_config')" -wireguard_config="${wireguard_config##*/}" +if bashio::config.has_value "wireguard_config"; then + wireguard_config="$(bashio::config 'wireguard_config')" + wireguard_config="${wireguard_config##*/}" +fi if [[ -z "${wireguard_config}" ]]; then bashio::log.info 'wireguard_config option left empty. Attempting automatic selection.' mapfile -t configs < <(find /config/wireguard -maxdepth 1 -type f -name '*.conf' -print) From 3e8add95ba7cd4964b67398b291e9be69d62b476 Mon Sep 17 00:00:00 2001 From: litinoveweedle <15144712+litinoveweedle@users.noreply.github.com> Date: Wed, 4 Feb 2026 15:19:28 +0100 Subject: [PATCH 29/35] remove unused /etc/openvpn directory --- .devcontainer/devcontainer.json | 29 +++++++++++++++++++------ .vscode/tasks.json | 4 ++-- qbittorrent/rootfs/etc/openvpn/.gitkeep | 0 3 files changed, 24 insertions(+), 9 deletions(-) delete mode 100644 qbittorrent/rootfs/etc/openvpn/.gitkeep diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index c701e2778..d0c285560 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,15 +1,27 @@ { "name": "Example devcontainer for add-on repositories", - "image": "ghcr.io/home-assistant/devcontainer:addons", - "appPort": ["7123:8123", "7357:4357"], - "postStartCommand": "sudo -E bash devcontainer_bootstrap", - "runArgs": ["-e", "GIT_EDITOR=code --wait", "--privileged"], + "image": "ghcr.io/home-assistant/devcontainer:2-addons", + "appPort": [ + "7123:8123", + "7357:4357" + ], + "postStartCommand": "bash devcontainer_bootstrap", + "runArgs": [ + "-e", + "GIT_EDITOR=code --wait", + "--privileged" + ], + "workspaceFolder": "/mnt/supervisor/addons/local/${localWorkspaceFolderBasename}", + "workspaceMount": "source=${localWorkspaceFolder},target=${containerWorkspaceFolder},type=bind,consistency=cached", "containerEnv": { "WORKSPACE_DIRECTORY": "${containerWorkspaceFolder}" }, "customizations": { "vscode": { - "extensions": ["timonwong.shellcheck", "esbenp.prettier-vscode"], + "extensions": [ + "timonwong.shellcheck", + "esbenp.prettier-vscode" + ], "settings": { "terminal.integrated.profiles.linux": { "zsh": { @@ -24,5 +36,8 @@ } } }, - "mounts": [ "type=volume,target=/var/lib/docker" ] -} + "mounts": [ + "type=volume,target=/var/lib/docker", + "type=volume,target=/mnt/supervisor" + ] +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json index f426c2c94..6f51015fc 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -4,7 +4,7 @@ { "label": "Start Home Assistant", "type": "shell", - "command": "sudo chmod a+x /usr/bin/supervisor* && sudo -E supervisor_run", + "command": "supervisor_run", "group": { "kind": "test", "isDefault": true @@ -16,4 +16,4 @@ "problemMatcher": [] } ] -} +} \ No newline at end of file diff --git a/qbittorrent/rootfs/etc/openvpn/.gitkeep b/qbittorrent/rootfs/etc/openvpn/.gitkeep deleted file mode 100644 index e69de29bb..000000000 From 6c40e39e8f05f5e8a908a88959e0877401caf1fc Mon Sep 17 00:00:00 2001 From: Alexandre <44178713+alexbelgium@users.noreply.github.com> Date: Wed, 4 Feb 2026 15:57:44 +0100 Subject: [PATCH 30/35] Add changelog for improved vpn script with proper recognition Enhanced the openvpn and wireguard scripts for improved robustness, security, and compatibility. --- qbittorrent/CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/qbittorrent/CHANGELOG.md b/qbittorrent/CHANGELOG.md index c604054de..1bc6d0465 100644 --- a/qbittorrent/CHANGELOG.md +++ b/qbittorrent/CHANGELOG.md @@ -1,3 +1,5 @@ +- Rewrite the openvpn and wireguard scripts in order to make them more robust, secure, and compatible with more suppliers @litinoveweedle + ## 5.1.4-6 (03-02-2026) - Minor bugs fixed ## 5.1.4-5 (23-01-2026) From c6104f5a82a4f7f9a130b17903fa3b73c71929f7 Mon Sep 17 00:00:00 2001 From: litinoveweedle <15144712+litinoveweedle@users.noreply.github.com> Date: Wed, 4 Feb 2026 16:17:49 +0100 Subject: [PATCH 31/35] fix vpn check script logic --- qbittorrent/rootfs/etc/services.d/vpn-monitor/run | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/qbittorrent/rootfs/etc/services.d/vpn-monitor/run b/qbittorrent/rootfs/etc/services.d/vpn-monitor/run index 4ed9f4812..52091f91a 100755 --- a/qbittorrent/rootfs/etc/services.d/vpn-monitor/run +++ b/qbittorrent/rootfs/etc/services.d/vpn-monitor/run @@ -144,14 +144,17 @@ if [[ "${vpn_openvpn}" == true && "${vpn_wireguard}" == true ]]; then exit 1 fi -if [[ "${vpn_openvpn}" == true ]] && ! bashio::config.true 'openvpn_alt_mode'; then +if [[ "${vpn_openvpn}" == true ]]; then VPN_INTERFACE=$(cat "/var/run/openvpn/interface") bashio::log.info "VPN monitor set to query external IP through interface ${VPN_INTERFACE} (interface binding)." elif [[ "${vpn_wireguard}" == true ]]; then VPN_INTERFACE=$(cat "/var/run/wireguard/interface") bashio::log.info "VPN monitor set to query external IP through interface ${VPN_INTERFACE} (interface binding)." -else - VPN_INTERFACE="" +fi +if [[ -z "${VPN_INTERFACE}" ]] || ! ip link show "${VPN_INTERFACE}" > /dev/null 2>&1 ; then + bashio::log.error "VPN interface not found." + bashio::addon.stop + exit 1 fi REAL_IP="$(read_real_ip)" From 6759f66a8ae671acc22cc5f42a8673a3c700c2e6 Mon Sep 17 00:00:00 2001 From: litinoveweedle <15144712+litinoveweedle@users.noreply.github.com> Date: Wed, 4 Feb 2026 19:14:06 +0100 Subject: [PATCH 32/35] fix openvpn options --- qbittorrent/rootfs/usr/local/sbin/vpn | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/qbittorrent/rootfs/usr/local/sbin/vpn b/qbittorrent/rootfs/usr/local/sbin/vpn index 72692c07f..489f85493 100755 --- a/qbittorrent/rootfs/usr/local/sbin/vpn +++ b/qbittorrent/rootfs/usr/local/sbin/vpn @@ -327,7 +327,7 @@ wireguard() { bashio::exit.nok 'WireGuard start failed.' fi - bashio::exit.nok 'WireGuard start failed.' + bashio::exit.nok 'WireGuard start failed.' } # --- OpenVPN Specific Logic --- @@ -349,13 +349,15 @@ _openvpn_up() { # Start OpenVPN in the background _cmd "/usr/sbin/openvpn \ --config "${config["ConfigFile"]}" \ - --script-security 2 --daemon --log /dev/null \ + --client \ + --daemon \ + --log /dev/null \ + --script-security 2 \ --auth-user-pass "${OPENVPN_STATE_DIR}/credentials.conf" \ --auth-retry none \ --up ${config["PostUpScript"]} \ - --up_delay 5 \ --down ${config["PostDownScript"]} \ - --down-delay 5 \ + --up-delay \ --up-restart \ --route-nopull \ --route-noexec" || return 1 @@ -401,6 +403,7 @@ openvpn() { # register up and down scripts bashio::log.info "Starting OpenVPN on interface ${config["Interface"]}..." if _openvpn_up; then + bashio::log.info "OpenVPN interface ${config["Interface"]} is up." bashio::exit.ok 'OpenVPN started.' fi bashio::log.error 'OpenVPN failed to establish connection.' @@ -408,16 +411,20 @@ openvpn() { elif [ "${mode}" = "down" ]; then bashio::log.info "Stopping OpenVPN on interface ${config["Interface"]}..." _openvpn_down + bashio::log.info "OpenVPN on interface ${config["Interface"]} is down." bashio::exit.ok 'OpenVPN stopped.' elif [ "${mode}" = "postup" ]; then _routing_add + bashio::exit.ok 'OpenVPN routes added.' elif [ "${mode}" = "postdown" ]; then _routing_del + bashio::exit.ok 'OpenVPN routes deleted.' else bashio::log.error "Invalid OpenVPN mode specified. Use 'up', 'down', 'postup', or 'postdown'." bashio::exit.nok 'OpenVPN start failed.' fi - bashio::log.info "Starting OpenVPN with configuration file: ${config_file}" + + bashio::exit.nok 'OpenVPN start failed.' } # --- Entry Point --- From 30123292aa04378fc4b34aebb1a0c10eb80472e0 Mon Sep 17 00:00:00 2001 From: litinoveweedle <15144712+litinoveweedle@users.noreply.github.com> Date: Wed, 4 Feb 2026 19:33:05 +0100 Subject: [PATCH 33/35] implement waiting for opevpn interface to come up --- qbittorrent/rootfs/usr/local/sbin/vpn | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/qbittorrent/rootfs/usr/local/sbin/vpn b/qbittorrent/rootfs/usr/local/sbin/vpn index 489f85493..2d2bd6290 100755 --- a/qbittorrent/rootfs/usr/local/sbin/vpn +++ b/qbittorrent/rootfs/usr/local/sbin/vpn @@ -361,6 +361,16 @@ _openvpn_up() { --up-restart \ --route-nopull \ --route-noexec" || return 1 + + #wait for slow OpenVPN interface to come up + for i in {1..10}; do + if ip link show "${config["Interface"]}" > /dev/null 2>&1 ; then + return 0 + fi + sleep 2 + done + bashio::log.error "OpenVPN interface ${config["Interface"]} failed to come up." + return 1 } _openvpn_down() { From 281dfea1b555a53f37f89508b3cff503f7bd2a8f Mon Sep 17 00:00:00 2001 From: litinoveweedle <15144712+litinoveweedle@users.noreply.github.com> Date: Wed, 4 Feb 2026 21:34:32 +0100 Subject: [PATCH 34/35] add hassio logging level --- qbittorrent/config.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/qbittorrent/config.yaml b/qbittorrent/config.yaml index 15cf183c6..196462649 100644 --- a/qbittorrent/config.yaml +++ b/qbittorrent/config.yaml @@ -79,6 +79,7 @@ map: - ssl name: qBittorrent options: + log_level: info env_vars: [] DNS_server: 8.8.8.8,1.1.1.1 PGID: "0" @@ -109,6 +110,7 @@ privileged: - DAC_READ_SEARCH - NET_ADMIN schema: + log_level: list(trace|debug|info|notice|warning|error|fatal)? env_vars: - name: match(^[A-Za-z0-9_]+$) value: str? From a70f6beb12ec7f8aebd4200e56755c825e3b1a99 Mon Sep 17 00:00:00 2001 From: Alexandre <44178713+alexbelgium@users.noreply.github.com> Date: Thu, 5 Feb 2026 07:32:20 +0100 Subject: [PATCH 35/35] Update qBittorrent version to 5.1.4-7 --- qbittorrent/config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qbittorrent/config.yaml b/qbittorrent/config.yaml index 196462649..1c1cb97f4 100644 --- a/qbittorrent/config.yaml +++ b/qbittorrent/config.yaml @@ -142,4 +142,4 @@ schema: slug: qbittorrent udev: true url: https://github.com/alexbelgium/hassio-addons -version: "5.1.4-6" +version: "5.1.4-7"