mirror of
https://github.com/alexbelgium/hassio-addons.git
synced 2026-02-05 19:34:52 +01:00
Merge pull request #2399 from litinoveweedle/qbittorrent_vpn_overhaul
Qbittorrent vpn overhaul
This commit is contained in:
@@ -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"
|
||||
]
|
||||
}
|
||||
4
.vscode/tasks.json
vendored
4
.vscode/tasks.json
vendored
@@ -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": []
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -112,7 +113,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"
|
||||
|
||||
# Automatic apps & bashio
|
||||
ADD "https://raw.githubusercontent.com/alexbelgium/hassio-addons/master/.templates/ha_autoapps.sh" "/ha_autoapps.sh"
|
||||
@@ -126,8 +127,6 @@ RUN chmod 744 /ha_autoapps.sh && /ha_autoapps.sh "$PACKAGES" && rm /ha_autoapps.
|
||||
# 4 Entrypoint #
|
||||
################
|
||||
|
||||
RUN chmod +x /usr/local/bin/* /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"
|
||||
|
||||
@@ -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"
|
||||
@@ -88,10 +89,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
|
||||
@@ -112,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?
|
||||
@@ -129,8 +128,7 @@ schema:
|
||||
keyfile: str
|
||||
localdisks: str?
|
||||
networkdisks: str?
|
||||
openvpn_alt_mode: bool?
|
||||
openvpn_config: str?
|
||||
openvpn_config: match(^\w+\.(ovpn|conf)$)?
|
||||
openvpn_enabled: bool?
|
||||
openvpn_password: str?
|
||||
openvpn_username: str?
|
||||
@@ -138,10 +136,10 @@ schema:
|
||||
run_duration: str?
|
||||
silent: bool?
|
||||
ssl: bool
|
||||
wireguard_config: str?
|
||||
wireguard_config: match(^\w+\.conf$)?
|
||||
wireguard_enabled: bool?
|
||||
whitelist: str?
|
||||
slug: qbittorrent
|
||||
udev: true
|
||||
url: https://github.com/alexbelgium/hassio-addons
|
||||
version: "5.1.4-6"
|
||||
version: "5.1.4-7"
|
||||
|
||||
@@ -2,269 +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
|
||||
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"
|
||||
|
||||
#########################
|
||||
# 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::fs.directory_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##*/}"
|
||||
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)
|
||||
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
|
||||
|
||||
interface_name="$(sed -n "/^dev tun/p" "${openvpn_config}" | awk -F' ' '{print $2}')"
|
||||
if [[ -z "${interface_name}" ]]; then
|
||||
bashio::exit.nok "OpenVPN configuration '${openvpn_config}' misses 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##*/}."
|
||||
|
||||
@@ -6,37 +6,32 @@ 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::fs.directory_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}"
|
||||
|
||||
if bashio::config.has_value 'wireguard_config'; then
|
||||
configured_name="$(bashio::config 'wireguard_config')"
|
||||
configured_name="${configured_name##*/}"
|
||||
if [[ -z "${configured_name}" ]]; 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}"
|
||||
else
|
||||
bashio::exit.nok "WireGuard configuration '/config/wireguard/${configured_name}' not found."
|
||||
fi
|
||||
fi
|
||||
bashio::log.info "------------------------------"
|
||||
bashio::log.info "Wireguard enabled, configuring"
|
||||
bashio::log.info "------------------------------"
|
||||
|
||||
if [ -z "${wireguard_config:-}" ]; then
|
||||
mapfile -t configs < <(find /config/wireguard -maxdepth 1 -type f -name '*.conf' -print)
|
||||
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)
|
||||
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
|
||||
@@ -48,10 +43,12 @@ if [ -z "${wireguard_config:-}" ]; then
|
||||
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/${wireguard_config}' not found."
|
||||
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}"
|
||||
|
||||
@@ -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 :
|
||||
@@ -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}"
|
||||
@@ -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 :
|
||||
@@ -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 ---
|
||||
|
||||
@@ -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,19 @@ if [[ "${vpn_openvpn}" == true && "${vpn_wireguard}" == true ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
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)."
|
||||
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)"
|
||||
|
||||
if [[ -n "${REAL_IP}" ]]; then
|
||||
|
||||
@@ -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 <metric> 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
|
||||
@@ -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}
|
||||
@@ -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}
|
||||
@@ -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}" "$@"
|
||||
453
qbittorrent/rootfs/usr/local/sbin/vpn
Executable file
453
qbittorrent/rootfs/usr/local/sbin/vpn
Executable file
@@ -0,0 +1,453 @@
|
||||
#!/usr/bin/with-contenv bashio
|
||||
# shellcheck shell=bash
|
||||
|
||||
# --- Common Functions ---
|
||||
|
||||
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"
|
||||
local line
|
||||
|
||||
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
|
||||
local key="${BASH_REMATCH[1]}"
|
||||
local value="${BASH_REMATCH[2]}"
|
||||
config_ref["$key"]="$value"
|
||||
fi
|
||||
done < "$config_file"
|
||||
}
|
||||
|
||||
_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 < <(echo "${dns_servers}" | tr -d ' ' | tr -d '\n')
|
||||
if [ ${config["IPv4Enabled"]} = "true" ]; then
|
||||
for dns_ip in "${dns_conf[@]}"; do
|
||||
local result=0
|
||||
_check_host "${dns_ip}" || result=$?
|
||||
if [ "${result}" -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
|
||||
local result=0
|
||||
_check_host "${dns_ip}" || result=$?
|
||||
if [ "${result}" -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
|
||||
fi
|
||||
}
|
||||
|
||||
_cmd() {
|
||||
cmd="$1"
|
||||
bashio::log.debug "Executing command: ${cmd}"
|
||||
eval "${cmd}"
|
||||
}
|
||||
|
||||
_check_host() {
|
||||
if ipcalc -c -4 "$1" >/dev/null 2>&1; then
|
||||
return 1 # IPv4
|
||||
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 hostnamee
|
||||
else
|
||||
return 0 # neither IP nor resolvable hostname
|
||||
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 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.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 ${dns_servers_ipv4[@]} ${dns_servers_ipv6[@]}; do
|
||||
echo "nameserver ${dns_ip}"
|
||||
done
|
||||
} > "${resolv_conf}"
|
||||
else
|
||||
bashio::exit.nok "Invalid resolvconf mode specified. Use 'update' or 'reset'."
|
||||
fi
|
||||
}
|
||||
|
||||
_resolve_hostname() {
|
||||
local hostname=$1
|
||||
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)
|
||||
|
||||
# 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+=("${ipv4_candidates[@]}")
|
||||
fi
|
||||
|
||||
echo "${ips[@]}"
|
||||
}
|
||||
|
||||
_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
|
||||
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
|
||||
_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 ${ipv6} table ${config["Table"]}" || return 1
|
||||
done
|
||||
|
||||
# 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
|
||||
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 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"]}"
|
||||
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."
|
||||
|
||||
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=""
|
||||
local -a local_ips=()
|
||||
mapfile -d ',' -t local_ips < <(echo "${config["Address"]}" | tr -d ' ')
|
||||
for local_ip in ${local_ips[@]}; do
|
||||
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 [ "${result}" -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
|
||||
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
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
_wireguard_down() {
|
||||
_routing_del
|
||||
_cmd "ip link set ${config["Interface"]} down" 2>/dev/null || true
|
||||
_cmd "ip link del ${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
|
||||
|
||||
bashio::log.info "Using Wireguard configuration file: ${config_file}"
|
||||
|
||||
_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 on interface ${config["Interface"]}..."
|
||||
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 [ "${result}" -eq 3 ]; then
|
||||
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.'
|
||||
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.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."
|
||||
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 on interface ${config["Interface"]}..."
|
||||
_wireguard_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'."
|
||||
bashio::exit.nok 'WireGuard start failed.'
|
||||
fi
|
||||
|
||||
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"]}"
|
||||
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 '#!/bin/bash' > ${config["PostUpScript"]}
|
||||
echo "${config["MySelf"]} openvpn postup" >> ${config["PostUpScript"]}
|
||||
chmod 755 ${config["PostUpScript"]}
|
||||
echo '#!/bin/bash' > ${config["PostDownScript"]}
|
||||
echo "${config["MySelf"]} openvpn postdown" >> ${config["PostDownScript"]}
|
||||
chmod 755 ${config["PostDownScript"]}
|
||||
|
||||
# Start OpenVPN in the background
|
||||
_cmd "/usr/sbin/openvpn \
|
||||
--config "${config["ConfigFile"]}" \
|
||||
--client \
|
||||
--daemon \
|
||||
--log /dev/null \
|
||||
--script-security 2 \
|
||||
--auth-user-pass "${OPENVPN_STATE_DIR}/credentials.conf" \
|
||||
--auth-retry none \
|
||||
--up ${config["PostUpScript"]} \
|
||||
--down ${config["PostDownScript"]} \
|
||||
--up-delay \
|
||||
--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() {
|
||||
# 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
|
||||
|
||||
bashio::log.warning "Using OpenVPN configuration file: ${config_file}"
|
||||
|
||||
_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 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.'
|
||||
_openvpn_down
|
||||
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::exit.nok 'OpenVPN start failed.'
|
||||
}
|
||||
|
||||
# --- Entry Point ---
|
||||
|
||||
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
|
||||
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
|
||||
Reference in New Issue
Block a user