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
This commit is contained in:
litinoveweedle
2026-01-26 18:11:13 +01:00
parent 7e0281bc21
commit 38c8929605

View File

@@ -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 <wireguard|openvpn> <up|down>"
bashio::exit.nok 'VPN start failed.'
fi
_parse_dns
if [[ "$1" == "wireguard" ]]; then
wireguard "$2"
elif [[ "$1" == "openvpn" ]]; then