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] 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