From 5f9be3c45e91d07b1d71f840589144e83fd6c197 Mon Sep 17 00:00:00 2001 From: Oleh Astappiev Date: Thu, 13 Feb 2025 11:17:43 +0100 Subject: [PATCH 01/13] feat(scripts): add script to update qBittorrent peer port --- Dockerfile | 1 + extras/scripts/qbittorrent-port-update.sh | 81 +++++++++++++++++++++++ 2 files changed, 82 insertions(+) create mode 100644 extras/scripts/qbittorrent-port-update.sh diff --git a/Dockerfile b/Dockerfile index 1415abcbe..c13d3df76 100644 --- a/Dockerfile +++ b/Dockerfile @@ -224,6 +224,7 @@ ENV VPN_SERVICE_PROVIDER=pia \ ENTRYPOINT ["/gluetun-entrypoint"] EXPOSE 8000/tcp 8888/tcp 8388/tcp 8388/udp HEALTHCHECK --interval=5s --timeout=5s --start-period=10s --retries=3 CMD /gluetun-entrypoint healthcheck +COPY extras/scripts/qbittorrent-port-update.sh /scripts/qbittorrent-port-update.sh ARG TARGETPLATFORM RUN apk add --no-cache --update -l wget && \ apk add --no-cache --update -X "https://dl-cdn.alpinelinux.org/alpine/v3.17/main" openvpn\~2.5 && \ diff --git a/extras/scripts/qbittorrent-port-update.sh b/extras/scripts/qbittorrent-port-update.sh new file mode 100644 index 000000000..4ce217254 --- /dev/null +++ b/extras/scripts/qbittorrent-port-update.sh @@ -0,0 +1,81 @@ +#!/bin/sh + +# Description: This script updates the peer port for the qBittorrent torrent client using its WebUI API. +# Note: For this to work, "Bypass authentication for clients on localhost" should be enabled +# in the WebUI settings (json key bypass_local_auth). + +build_default_url() { + port="${1:-$WEBUI_PORT}" + echo "http://127.0.0.1:${port}/api/v2/app/setPreferences" +} + +# default values +WEBUI_PORT="8080" +DEFAULT_URL=$(build_default_url) +WGET_OPTS="" + +usage() { + echo "Usage: $0 [OPTIONS]" + echo "" + echo "Update qBittorrent peer-port via API." + echo "" + echo "Options:" + echo " -h, --help Show this help message and exit." + echo " -P, --port PORT Specify the Transmission Peer Port." + echo " REQUIRED" + echo " -W, --webui-port PORT Specify the qBittorrent WebUI Port." + echo " Default: ${WEBUI_PORT}" + echo " -U, --url URL Specify the qBittorrent API URL." + echo " DEFAULT: ${DEFAULT_URL}" + echo " Overrides --webui option." + echo "Example:" + echo " $0 --port 40409" + exit 1 +} + +while [ $# -gt 0 ]; do + case "$1" in + -h | --help) + usage + ;; + -P | --port) + PORTS="$2" + PORT=$(echo "$PORTS" | cut -d',' -f1) + shift 2 + ;; + -W | --webui-port) + WEBUI_PORT="$2" + shift 2 + ;; + -U | --url) + PREF_URL="$2" + shift 2 + ;; + *) + echo "Unknown option: $1" + usage + ;; + esac +done + +if [ -z "${PORT}" ]; then + echo "ERROR: No PORT provided!" + exit 1 +fi + +if [ -z "${PREF_URL+x}" ]; then + PREF_URL=$(build_default_url) +fi + +# update peer host via API +wget --retry-connrefused -qO- ${WGET_OPTS} --post-data="json={\"random_port\":false}" "$PREF_URL" +wget ${WGET_OPTS} -qO- --post-data="json={\"listen_port\":$PORT}" "$PREF_URL" + +# check if wget command succeeded +if [ $? -ne 0 ]; then + echo "ERROR: Could not update peer port" + exit 1 +fi + +echo "Success! Peer port updated to ${PORT}" +exit 0 From 1932f9e460488af7a0af1a9f2d5c6a3237c51ca9 Mon Sep 17 00:00:00 2001 From: Oleh Astappiev Date: Sat, 15 Feb 2025 00:12:12 +0100 Subject: [PATCH 02/13] feat: add option to specify user/pass and authorize on qBittorrent --- extras/scripts/qbittorrent-port-update.sh | 69 ++++++++++++++++++----- 1 file changed, 56 insertions(+), 13 deletions(-) diff --git a/extras/scripts/qbittorrent-port-update.sh b/extras/scripts/qbittorrent-port-update.sh index 4ce217254..b04df2e8e 100644 --- a/extras/scripts/qbittorrent-port-update.sh +++ b/extras/scripts/qbittorrent-port-update.sh @@ -1,42 +1,56 @@ #!/bin/sh -# Description: This script updates the peer port for the qBittorrent torrent client using its WebUI API. +# Description: This script updates the peer-port for the qBittorrent torrent client using its WebUI API. # Note: For this to work, "Bypass authentication for clients on localhost" should be enabled # in the WebUI settings (json key bypass_local_auth). build_default_url() { port="${1:-$WEBUI_PORT}" - echo "http://127.0.0.1:${port}/api/v2/app/setPreferences" + echo "http://127.0.0.1:${port}/api" } # default values WEBUI_PORT="8080" DEFAULT_URL=$(build_default_url) -WGET_OPTS="" +WGET_OPTS="--retry-connrefused --tries=5" usage() { echo "Usage: $0 [OPTIONS]" echo "" - echo "Update qBittorrent peer-port via API." + echo "Update qBittorrent peer-port via API" echo "" echo "Options:" echo " -h, --help Show this help message and exit." - echo " -P, --port PORT Specify the Transmission Peer Port." + echo " -u, --user USER Specify the qBittorrent username." + echo " (Omit if not required)" + echo " -p, --pass PASS Specify the qBittorrent password." + echo " (Omit if not required)" + echo " -P, --port PORT Specify the qBittorrent peer-port." echo " REQUIRED" echo " -W, --webui-port PORT Specify the qBittorrent WebUI Port." echo " Default: ${WEBUI_PORT}" echo " -U, --url URL Specify the qBittorrent API URL." echo " DEFAULT: ${DEFAULT_URL}" - echo " Overrides --webui option." + echo " Overrides --webui-port option." echo "Example:" - echo " $0 --port 40409" - exit 1 + echo " $0 -u admin -p **** --port 40409" } while [ $# -gt 0 ]; do case "$1" in -h | --help) usage + exit 0 + ;; + -u | --user) + USERNAME="$2" + _USECRED=true + shift 2 + ;; + -p | --pass) + PASSWORD="$2" + _USECRED=true + shift 2 ;; -P | --port) PORTS="$2" @@ -54,12 +68,13 @@ while [ $# -gt 0 ]; do *) echo "Unknown option: $1" usage + exit 1 ;; esac done if [ -z "${PORT}" ]; then - echo "ERROR: No PORT provided!" + echo "ERROR: No qBittorrent peer-port provided!" exit 1 fi @@ -67,15 +82,43 @@ if [ -z "${PREF_URL+x}" ]; then PREF_URL=$(build_default_url) fi +if [ "${_USECRED}" ]; then + # make sure username AND password were provided + if [ -z "${USERNAME}" ]; then + echo "ERROR: qBittorrent username not provided." + exit 1 + fi + if [ -z "${PASSWORD}" ]; then + echo "ERROR: qBittorrent password not provided." + exit 1 + fi + + cookie=$(wget ${WGET_OPTS} -qO- \ + --header "Referer: ${PREF_URL}" \ + --post-data "username=${USERNAME}&password=${PASSWORD}" \ + "${PREF_URL}/v2/auth/login" \ + --server-response 2>&1 | \ + grep -i "set-cookie:" | \ + sed 's/.*set-cookie: //I;s/;.*//') + + if [ -z "${cookie}" ]; then + echo "ERROR: Could not authenticate with qBittorrent." + exit 1 + fi + + # set cookie for future requests + WGET_OPTS="${WGET_OPTS} --header=Cookie:$cookie" +fi + # update peer host via API -wget --retry-connrefused -qO- ${WGET_OPTS} --post-data="json={\"random_port\":false}" "$PREF_URL" -wget ${WGET_OPTS} -qO- --post-data="json={\"listen_port\":$PORT}" "$PREF_URL" +wget ${WGET_OPTS} -qO- --post-data="json={\"random_port\":false}" "$PREF_URL/v2/app/setPreferences" +wget ${WGET_OPTS} -qO- --post-data="json={\"listen_port\":$PORT}" "$PREF_URL/v2/app/setPreferences" # check if wget command succeeded if [ $? -ne 0 ]; then - echo "ERROR: Could not update peer port" + echo "ERROR: Could not update qBittorrent peer-port." exit 1 fi -echo "Success! Peer port updated to ${PORT}" +echo "Success! qBittorrent peer-port updated to ${PORT}" exit 0 From 839e318c9c30676c8ecd9f042c524e87a1634f32 Mon Sep 17 00:00:00 2001 From: Oleh Astappiev <4512729+astappiev@users.noreply.github.com> Date: Thu, 13 Nov 2025 21:58:35 +0100 Subject: [PATCH 03/13] feat(scripts): address minor issues in the script --- extras/scripts/qbittorrent-port-update.sh | 65 +++++++++++++---------- 1 file changed, 36 insertions(+), 29 deletions(-) diff --git a/extras/scripts/qbittorrent-port-update.sh b/extras/scripts/qbittorrent-port-update.sh index b04df2e8e..50f51fe9e 100644 --- a/extras/scripts/qbittorrent-port-update.sh +++ b/extras/scripts/qbittorrent-port-update.sh @@ -1,8 +1,12 @@ #!/bin/sh # Description: This script updates the peer-port for the qBittorrent torrent client using its WebUI API. -# Note: For this to work, "Bypass authentication for clients on localhost" should be enabled -# in the WebUI settings (json key bypass_local_auth). +# +# How to use: +# 1. (Optional) Disable authentication for localhost clients in qBittorrent WebUI settings ("Bypass authentication for clients on localhost" or `bypass_local_auth` in json). +# 2. Set the environment variable: +# VPN_PORT_FORWARDING_UP_COMMAND=/bin/sh -c "/scripts/qbittorrent-port-update.sh --port {{PORTS}} --webui-port 9081" +# Alternatively, you can use `--user` and `--pass` options to provide credentials. build_default_url() { port="${1:-$WEBUI_PORT}" @@ -20,48 +24,47 @@ usage() { echo "Update qBittorrent peer-port via API" echo "" echo "Options:" - echo " -h, --help Show this help message and exit." - echo " -u, --user USER Specify the qBittorrent username." - echo " (Omit if not required)" - echo " -p, --pass PASS Specify the qBittorrent password." - echo " (Omit if not required)" - echo " -P, --port PORT Specify the qBittorrent peer-port." - echo " REQUIRED" - echo " -W, --webui-port PORT Specify the qBittorrent WebUI Port." - echo " Default: ${WEBUI_PORT}" - echo " -U, --url URL Specify the qBittorrent API URL." - echo " DEFAULT: ${DEFAULT_URL}" - echo " Overrides --webui-port option." + echo " --help Show this help message and exit." + echo " --user USER Specify the qBittorrent username." + echo " (Omit if not required)" + echo " --pass PASS Specify the qBittorrent password." + echo " (Omit if not required)" + echo " --port PORT Specify the qBittorrent peer-port." + echo " REQUIRED" + echo " --webui-port PORT Specify the qBittorrent WebUI Port." + echo " Default: ${WEBUI_PORT}" + echo " --url URL Specify the qBittorrent API URL." + echo " Default: ${DEFAULT_URL}" + echo " Overrides --webui-port option." echo "Example:" - echo " $0 -u admin -p **** --port 40409" + echo " $0 --user admin --pass **** --port 40409" } while [ $# -gt 0 ]; do case "$1" in - -h | --help) + --help) usage exit 0 ;; - -u | --user) + --user) USERNAME="$2" _USECRED=true shift 2 ;; - -p | --pass) + --pass) PASSWORD="$2" _USECRED=true shift 2 ;; - -P | --port) - PORTS="$2" - PORT=$(echo "$PORTS" | cut -d',' -f1) + --port) + PORT=$(echo "$2" | cut -d',' -f1) shift 2 ;; - -W | --webui-port) + --webui-port) WEBUI_PORT="$2" shift 2 ;; - -U | --url) + --url) PREF_URL="$2" shift 2 ;; @@ -74,7 +77,7 @@ while [ $# -gt 0 ]; do done if [ -z "${PORT}" ]; then - echo "ERROR: No qBittorrent peer-port provided!" + echo "ERROR: --port is required but not provided" exit 1 fi @@ -110,13 +113,17 @@ if [ "${_USECRED}" ]; then WGET_OPTS="${WGET_OPTS} --header=Cookie:$cookie" fi -# update peer host via API -wget ${WGET_OPTS} -qO- --post-data="json={\"random_port\":false}" "$PREF_URL/v2/app/setPreferences" -wget ${WGET_OPTS} -qO- --post-data="json={\"listen_port\":$PORT}" "$PREF_URL/v2/app/setPreferences" +# update peer host via API, 0 is a dummy port, required due to https://github.com/qdm12/gluetun-wiki/pull/147 +wget ${WGET_OPTS} -qO- --post-data="json={\"random_port\":false,\"upnp\":false,\"listen_port\":0}" "$PREF_URL/v2/app/setPreferences" +if [ $? -ne 0 ]; then + echo "ERROR: Could not update qBittorrent peer-port (first call failed)." + exit 1 +fi -# check if wget command succeeded +# second call to set the actual port +wget ${WGET_OPTS} -qO- --post-data="json={\"listen_port\":$PORT}" "$PREF_URL/v2/app/setPreferences" if [ $? -ne 0 ]; then - echo "ERROR: Could not update qBittorrent peer-port." + echo "ERROR: Could not update qBittorrent peer-port (second call failed)." exit 1 fi From 47ab4466022f5f16de7bd94bbf72644e0bae366c Mon Sep 17 00:00:00 2001 From: Oleh Astappiev <4512729+astappiev@users.noreply.github.com> Date: Sat, 22 Nov 2025 22:51:12 +0100 Subject: [PATCH 04/13] feat(scripts): add options for interface and address --- extras/scripts/qbittorrent-port-update.sh | 46 +++++++++++++++-------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/extras/scripts/qbittorrent-port-update.sh b/extras/scripts/qbittorrent-port-update.sh index 50f51fe9e..94c9198e1 100644 --- a/extras/scripts/qbittorrent-port-update.sh +++ b/extras/scripts/qbittorrent-port-update.sh @@ -5,7 +5,7 @@ # How to use: # 1. (Optional) Disable authentication for localhost clients in qBittorrent WebUI settings ("Bypass authentication for clients on localhost" or `bypass_local_auth` in json). # 2. Set the environment variable: -# VPN_PORT_FORWARDING_UP_COMMAND=/bin/sh -c "/scripts/qbittorrent-port-update.sh --port {{PORTS}} --webui-port 9081" +# VPN_PORT_FORWARDING_UP_COMMAND=/bin/sh -c "/scripts/qbittorrent-port-update.sh --port {{PORT}} --webui-port 9081" # Alternatively, you can use `--user` and `--pass` options to provide credentials. build_default_url() { @@ -14,8 +14,13 @@ build_default_url() { } # default values +VPN_PORT="" +VPN_INTERFACE="tun0" +VPN_ADDRESS="0.0.0.0" WEBUI_PORT="8080" -DEFAULT_URL=$(build_default_url) +WEBUI_URL=$(build_default_url "$WEBUI_PORT") + +# it might take a few tries for qBittorrent to be available (e.g. slow loading with many torrents) WGET_OPTS="--retry-connrefused --tries=5" usage() { @@ -31,10 +36,14 @@ usage() { echo " (Omit if not required)" echo " --port PORT Specify the qBittorrent peer-port." echo " REQUIRED" + echo " --iface IFACE Specify the VPN interface to bind to." + echo " Default: ${VPN_INTERFACE}" + echo " --addr ADDR Specify the VPN interface address to bind to." + echo " Default: ${VPN_ADDRESS}" echo " --webui-port PORT Specify the qBittorrent WebUI Port." echo " Default: ${WEBUI_PORT}" - echo " --url URL Specify the qBittorrent API URL." - echo " Default: ${DEFAULT_URL}" + echo " --url URL Specify the qBittorrent API URL. Not compatible with --webui-port." + echo " Default: ${WEBUI_URL}" echo " Overrides --webui-port option." echo "Example:" echo " $0 --user admin --pass **** --port 40409" @@ -57,15 +66,24 @@ while [ $# -gt 0 ]; do shift 2 ;; --port) - PORT=$(echo "$2" | cut -d',' -f1) + VPN_PORT=$(echo "$2" | cut -d',' -f1) + shift 2 + ;; + --iface) + VPN_INTERFACE="$2" + shift 2 + ;; + --addr) + VPN_ADDRESS="$2" shift 2 ;; --webui-port) WEBUI_PORT="$2" + WEBUI_URL=$(build_default_url "$WEBUI_PORT") shift 2 ;; --url) - PREF_URL="$2" + WEBUI_URL="$2" shift 2 ;; *) @@ -76,15 +94,11 @@ while [ $# -gt 0 ]; do esac done -if [ -z "${PORT}" ]; then +if [ -z "${VPN_PORT}" ]; then echo "ERROR: --port is required but not provided" exit 1 fi -if [ -z "${PREF_URL+x}" ]; then - PREF_URL=$(build_default_url) -fi - if [ "${_USECRED}" ]; then # make sure username AND password were provided if [ -z "${USERNAME}" ]; then @@ -97,9 +111,9 @@ if [ "${_USECRED}" ]; then fi cookie=$(wget ${WGET_OPTS} -qO- \ - --header "Referer: ${PREF_URL}" \ + --header "Referer: ${WEBUI_URL}" \ --post-data "username=${USERNAME}&password=${PASSWORD}" \ - "${PREF_URL}/v2/auth/login" \ + "${WEBUI_URL}/v2/auth/login" \ --server-response 2>&1 | \ grep -i "set-cookie:" | \ sed 's/.*set-cookie: //I;s/;.*//') @@ -114,18 +128,18 @@ if [ "${_USECRED}" ]; then fi # update peer host via API, 0 is a dummy port, required due to https://github.com/qdm12/gluetun-wiki/pull/147 -wget ${WGET_OPTS} -qO- --post-data="json={\"random_port\":false,\"upnp\":false,\"listen_port\":0}" "$PREF_URL/v2/app/setPreferences" +wget ${WGET_OPTS} -qO- --post-data="json={\"random_port\":false,\"upnp\":false,\"listen_port\":0,\"current_network_interface\":\"lo\",\"current_interface_address\":\"\"}" "$WEBUI_URL/v2/app/setPreferences" if [ $? -ne 0 ]; then echo "ERROR: Could not update qBittorrent peer-port (first call failed)." exit 1 fi # second call to set the actual port -wget ${WGET_OPTS} -qO- --post-data="json={\"listen_port\":$PORT}" "$PREF_URL/v2/app/setPreferences" +wget ${WGET_OPTS} -qO- --post-data="json={\"listen_port\":$VPN_PORT,\"current_network_interface\":\"$VPN_INTERFACE\",\"current_interface_address\":\"$VPN_ADDRESS\"}" "$WEBUI_URL/v2/app/setPreferences" if [ $? -ne 0 ]; then echo "ERROR: Could not update qBittorrent peer-port (second call failed)." exit 1 fi -echo "Success! qBittorrent peer-port updated to ${PORT}" +echo "Success! qBittorrent peer-port updated to ${VPN_PORT}" exit 0 From 8d55d4a659e1682877d712bbd6b0e739c5e77903 Mon Sep 17 00:00:00 2001 From: Oleh Astappiev <4512729+astappiev@users.noreply.github.com> Date: Sun, 23 Nov 2025 11:39:36 +0100 Subject: [PATCH 05/13] chore(scripts): update docs to include `addr` options --- extras/scripts/qbittorrent-port-update.sh | 24 +++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/extras/scripts/qbittorrent-port-update.sh b/extras/scripts/qbittorrent-port-update.sh index 94c9198e1..a6a2ec047 100644 --- a/extras/scripts/qbittorrent-port-update.sh +++ b/extras/scripts/qbittorrent-port-update.sh @@ -3,10 +3,10 @@ # Description: This script updates the peer-port for the qBittorrent torrent client using its WebUI API. # # How to use: -# 1. (Optional) Disable authentication for localhost clients in qBittorrent WebUI settings ("Bypass authentication for clients on localhost" or `bypass_local_auth` in json). -# 2. Set the environment variable: -# VPN_PORT_FORWARDING_UP_COMMAND=/bin/sh -c "/scripts/qbittorrent-port-update.sh --port {{PORT}} --webui-port 9081" -# Alternatively, you can use `--user` and `--pass` options to provide credentials. +# 1. Provide username and password via `--user` and `--pass` options. +# 2. (Alternative) Disable authentication for localhost clients in qBittorrent WebUI settings ("Bypass authentication for clients on localhost" or `bypass_local_auth` in json). +# 3. Set the environment variable: +# VPN_PORT_FORWARDING_UP_COMMAND=/bin/sh -c "/scripts/qbittorrent-port-update.sh [--user USER --pass PASS] --port {{PORT}} --iface {{VPN_INTERFACE}} --webui-port 9081" build_default_url() { port="${1:-$WEBUI_PORT}" @@ -16,7 +16,7 @@ build_default_url() { # default values VPN_PORT="" VPN_INTERFACE="tun0" -VPN_ADDRESS="0.0.0.0" +VPN_ADDRESS="" WEBUI_PORT="8080" WEBUI_URL=$(build_default_url "$WEBUI_PORT") @@ -37,16 +37,16 @@ usage() { echo " --port PORT Specify the qBittorrent peer-port." echo " REQUIRED" echo " --iface IFACE Specify the VPN interface to bind to." - echo " Default: ${VPN_INTERFACE}" + echo " Default: \"${VPN_INTERFACE}\"" echo " --addr ADDR Specify the VPN interface address to bind to." - echo " Default: ${VPN_ADDRESS}" - echo " --webui-port PORT Specify the qBittorrent WebUI Port." - echo " Default: ${WEBUI_PORT}" + echo " Available options: empty = All addresses, 0.0.0.0 - All IPv4 addresses, :: - All IPv6 addresses, or a specific IP address" + echo " Default: \"${VPN_ADDRESS}\"" + echo " --webui-port PORT Specify the qBittorrent WebUI Port. Not compatible with --url." + echo " Default: \"${WEBUI_PORT}\"" echo " --url URL Specify the qBittorrent API URL. Not compatible with --webui-port." - echo " Default: ${WEBUI_URL}" - echo " Overrides --webui-port option." + echo " Default: \"${WEBUI_URL}\"" echo "Example:" - echo " $0 --user admin --pass **** --port 40409" + echo " $0 --user ADMIN --pass **** --port 40409" } while [ $# -gt 0 ]; do From d0470fabd1296c5dddbbf5528967e474ba8d01be Mon Sep 17 00:00:00 2001 From: Oleh Astappiev <4512729+astappiev@users.noreply.github.com> Date: Sun, 23 Nov 2025 11:56:55 +0100 Subject: [PATCH 06/13] chore: update message --- extras/scripts/qbittorrent-port-update.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/extras/scripts/qbittorrent-port-update.sh b/extras/scripts/qbittorrent-port-update.sh index a6a2ec047..d2609381c 100644 --- a/extras/scripts/qbittorrent-port-update.sh +++ b/extras/scripts/qbittorrent-port-update.sh @@ -141,5 +141,4 @@ if [ $? -ne 0 ]; then exit 1 fi -echo "Success! qBittorrent peer-port updated to ${VPN_PORT}" -exit 0 +echo "qBittorrent peer-port updated to ${VPN_PORT}" From 6d2d5a42db629cac36dde31c9611170b97338a77 Mon Sep 17 00:00:00 2001 From: Oleh Astappiev <4512729+astappiev@users.noreply.github.com> Date: Sun, 23 Nov 2025 12:46:00 +0100 Subject: [PATCH 07/13] chore(scripts): update qbittorent script help --- extras/scripts/qbittorrent-port-update.sh | 68 ++++++++++++----------- 1 file changed, 37 insertions(+), 31 deletions(-) diff --git a/extras/scripts/qbittorrent-port-update.sh b/extras/scripts/qbittorrent-port-update.sh index d2609381c..be849a276 100644 --- a/extras/scripts/qbittorrent-port-update.sh +++ b/extras/scripts/qbittorrent-port-update.sh @@ -1,13 +1,5 @@ #!/bin/sh -# Description: This script updates the peer-port for the qBittorrent torrent client using its WebUI API. -# -# How to use: -# 1. Provide username and password via `--user` and `--pass` options. -# 2. (Alternative) Disable authentication for localhost clients in qBittorrent WebUI settings ("Bypass authentication for clients on localhost" or `bypass_local_auth` in json). -# 3. Set the environment variable: -# VPN_PORT_FORWARDING_UP_COMMAND=/bin/sh -c "/scripts/qbittorrent-port-update.sh [--user USER --pass PASS] --port {{PORT}} --iface {{VPN_INTERFACE}} --webui-port 9081" - build_default_url() { port="${1:-$WEBUI_PORT}" echo "http://127.0.0.1:${port}/api" @@ -26,27 +18,40 @@ WGET_OPTS="--retry-connrefused --tries=5" usage() { echo "Usage: $0 [OPTIONS]" echo "" - echo "Update qBittorrent peer-port via API" + echo "Update qBittorrent listening port, network interface, and address via its WebUI API." + echo "This script is designed to work with Gluetun's VPN_PORT_FORWARDING_UP_COMMAND." + echo "" + echo "WARNING: If you do not provide --iface and --addr, they will be set to default values on every run" echo "" echo "Options:" - echo " --help Show this help message and exit." - echo " --user USER Specify the qBittorrent username." - echo " (Omit if not required)" - echo " --pass PASS Specify the qBittorrent password." - echo " (Omit if not required)" - echo " --port PORT Specify the qBittorrent peer-port." + echo " --help Show this help message and exit" + echo " --user USER Specify the qBittorrent username" + echo " (Omit if authentication is disabled for localhost)" + echo " --pass PASS Specify the qBittorrent password" + echo " (Omit if authentication is disabled for localhost)" + echo " --port PORT Specify the qBittorrent listening port (peer-port)" echo " REQUIRED" - echo " --iface IFACE Specify the VPN interface to bind to." + echo " --iface IFACE Specify the network interface to bind to" + echo " Examples: \"\" (any interface), \"lo\", \"eth0\", \"tun0\", etc." echo " Default: \"${VPN_INTERFACE}\"" - echo " --addr ADDR Specify the VPN interface address to bind to." - echo " Available options: empty = All addresses, 0.0.0.0 - All IPv4 addresses, :: - All IPv6 addresses, or a specific IP address" + echo " --addr ADDR Specify the network address to bind to" + echo " Examples: \"\" (all addresses), \"0.0.0.0\" (all IPv4), \"::\" (all IPv6), or a specific IP" echo " Default: \"${VPN_ADDRESS}\"" - echo " --webui-port PORT Specify the qBittorrent WebUI Port. Not compatible with --url." + echo " --webui-port PORT Specify the qBittorrent WebUI Port. Not compatible with --url" echo " Default: \"${WEBUI_PORT}\"" - echo " --url URL Specify the qBittorrent API URL. Not compatible with --webui-port." + echo " --url URL Specify the qBittorrent API URL. Not compatible with --webui-port" echo " Default: \"${WEBUI_URL}\"" - echo "Example:" - echo " $0 --user ADMIN --pass **** --port 40409" + echo "" + echo "Gluetun Placeholders (available in VPN_PORT_FORWARDING_UP_COMMAND):" + echo " {{PORT}} Replaced by the forwarded port number (or first port if multiple)" + echo " {{PORTS}} Replaced by the forwarded port numbers (comma separated)" + echo " {{VPN_INTERFACE}} Replaced by the VPN interface name (e.g. tun0)" + echo "" + echo "Example commands (set as value of VPN_PORT_FORWARDING_DOWN_COMMAND):" + echo "# With authentication:" + echo "/bin/sh -c \"/scripts/qbittorrent-port-update.sh --user ADMIN --pass **** --port {{PORT}} --iface {{VPN_INTERFACE}} --webui-port 8080\"" + echo "# Without authentication (\"Bypass authentication for clients on localhost\" enabled in qBittorrent):" + echo "/bin/sh -c \"/scripts/qbittorrent-port-update.sh --port {{PORT}} --iface {{VPN_INTERFACE}} --webui-port 8080\"" } while [ $# -gt 0 ]; do @@ -102,11 +107,11 @@ fi if [ "${_USECRED}" ]; then # make sure username AND password were provided if [ -z "${USERNAME}" ]; then - echo "ERROR: qBittorrent username not provided." + echo "ERROR: qBittorrent username not provided" exit 1 fi if [ -z "${PASSWORD}" ]; then - echo "ERROR: qBittorrent password not provided." + echo "ERROR: qBittorrent password not provided" exit 1 fi @@ -119,7 +124,7 @@ if [ "${_USECRED}" ]; then sed 's/.*set-cookie: //I;s/;.*//') if [ -z "${cookie}" ]; then - echo "ERROR: Could not authenticate with qBittorrent." + echo "ERROR: Failed to authenticate with qBittorrent. Check username/password or verify WebUI is accessible" exit 1 fi @@ -127,18 +132,19 @@ if [ "${_USECRED}" ]; then WGET_OPTS="${WGET_OPTS} --header=Cookie:$cookie" fi -# update peer host via API, 0 is a dummy port, required due to https://github.com/qdm12/gluetun-wiki/pull/147 -wget ${WGET_OPTS} -qO- --post-data="json={\"random_port\":false,\"upnp\":false,\"listen_port\":0,\"current_network_interface\":\"lo\",\"current_interface_address\":\"\"}" "$WEBUI_URL/v2/app/setPreferences" +# update qBittorrent preferences via API, the first call disabled everything and sets safe defaults +# This is required as per https://github.com/qdm12/gluetun-wiki/pull/147 and https://github.com/qdm12/gluetun/issues/2997#issuecomment-3566749335 +wget ${WGET_OPTS} -qO- --post-data="json={\"random_port\":false,\"upnp\":false,\"listen_port\":0,\"current_network_interface\":\"lo\",\"current_interface_address\":\"127.0.0.1\"}" "$WEBUI_URL/v2/app/setPreferences" if [ $? -ne 0 ]; then - echo "ERROR: Could not update qBittorrent peer-port (first call failed)." + echo "ERROR: Failed to reset qBittorrent settings" exit 1 fi -# second call to set the actual port +# second call to set the actual port, interface and address wget ${WGET_OPTS} -qO- --post-data="json={\"listen_port\":$VPN_PORT,\"current_network_interface\":\"$VPN_INTERFACE\",\"current_interface_address\":\"$VPN_ADDRESS\"}" "$WEBUI_URL/v2/app/setPreferences" if [ $? -ne 0 ]; then - echo "ERROR: Could not update qBittorrent peer-port (second call failed)." + echo "ERROR: Failed to apply qBittorrent port/interface settings" exit 1 fi -echo "qBittorrent peer-port updated to ${VPN_PORT}" +echo "qBittorrent updated to use peer-port: ${VPN_PORT}, interface: \"${VPN_INTERFACE}\", address: \"${VPN_ADDRESS}\"" From b59e869a22d52d875c689f39ffab3c2cf0a21c06 Mon Sep 17 00:00:00 2001 From: Quentin McGaw Date: Tue, 3 Mar 2026 16:56:22 +0100 Subject: [PATCH 08/13] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- extras/scripts/qbittorrent-port-update.sh | 39 +++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/extras/scripts/qbittorrent-port-update.sh b/extras/scripts/qbittorrent-port-update.sh index be849a276..9c24c5555 100644 --- a/extras/scripts/qbittorrent-port-update.sh +++ b/extras/scripts/qbittorrent-port-update.sh @@ -47,7 +47,7 @@ usage() { echo " {{PORTS}} Replaced by the forwarded port numbers (comma separated)" echo " {{VPN_INTERFACE}} Replaced by the VPN interface name (e.g. tun0)" echo "" - echo "Example commands (set as value of VPN_PORT_FORWARDING_DOWN_COMMAND):" + echo "Example commands (set as value of VPN_PORT_FORWARDING_UP_COMMAND):" echo "# With authentication:" echo "/bin/sh -c \"/scripts/qbittorrent-port-update.sh --user ADMIN --pass **** --port {{PORT}} --iface {{VPN_INTERFACE}} --webui-port 8080\"" echo "# Without authentication (\"Bypass authentication for clients on localhost\" enabled in qBittorrent):" @@ -61,33 +61,68 @@ while [ $# -gt 0 ]; do exit 0 ;; --user) + if [ $# -lt 2 ] || [ -z "$2" ]; then + echo "Error: --user requires a non-empty argument." + usage + exit 1 + fi USERNAME="$2" _USECRED=true shift 2 ;; --pass) + if [ $# -lt 2 ] || [ -z "$2" ]; then + echo "Error: --pass requires a non-empty argument." + usage + exit 1 + fi PASSWORD="$2" _USECRED=true shift 2 ;; --port) + if [ $# -lt 2 ] || [ -z "$2" ]; then + echo "Error: --port requires a non-empty argument." + usage + exit 1 + fi VPN_PORT=$(echo "$2" | cut -d',' -f1) shift 2 ;; --iface) + if [ $# -lt 2 ] || [ -z "$2" ]; then + echo "Error: --iface requires a non-empty argument." + usage + exit 1 + fi VPN_INTERFACE="$2" shift 2 ;; --addr) + if [ $# -lt 2 ] || [ -z "$2" ]; then + echo "Error: --addr requires a non-empty argument." + usage + exit 1 + fi VPN_ADDRESS="$2" shift 2 ;; --webui-port) + if [ $# -lt 2 ] || [ -z "$2" ]; then + echo "Error: --webui-port requires a non-empty argument." + usage + exit 1 + fi WEBUI_PORT="$2" WEBUI_URL=$(build_default_url "$WEBUI_PORT") shift 2 ;; --url) + if [ $# -lt 2 ] || [ -z "$2" ]; then + echo "Error: --url requires a non-empty argument." + usage + exit 1 + fi WEBUI_URL="$2" shift 2 ;; @@ -104,7 +139,7 @@ if [ -z "${VPN_PORT}" ]; then exit 1 fi -if [ "${_USECRED}" ]; then +if [ "${_USECRED}" = "true" ]; then # make sure username AND password were provided if [ -z "${USERNAME}" ]; then echo "ERROR: qBittorrent username not provided" From 0554e69bf5d04fc8de0ec965adbd7ce0952b6308 Mon Sep 17 00:00:00 2001 From: Quentin McGaw Date: Tue, 3 Mar 2026 17:47:39 +0000 Subject: [PATCH 09/13] add both up and down examples --- extras/scripts/qbittorrent-port-update.sh | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/extras/scripts/qbittorrent-port-update.sh b/extras/scripts/qbittorrent-port-update.sh index 9c24c5555..8c2a7e000 100644 --- a/extras/scripts/qbittorrent-port-update.sh +++ b/extras/scripts/qbittorrent-port-update.sh @@ -19,7 +19,8 @@ usage() { echo "Usage: $0 [OPTIONS]" echo "" echo "Update qBittorrent listening port, network interface, and address via its WebUI API." - echo "This script is designed to work with Gluetun's VPN_PORT_FORWARDING_UP_COMMAND." + echo "This script is designed to work with Gluetun's VPN_PORT_FORWARDING_UP_COMMAND" + echo "and VPN_PORT_FORWARDING_DOWN_COMMAND." echo "" echo "WARNING: If you do not provide --iface and --addr, they will be set to default values on every run" echo "" @@ -47,11 +48,13 @@ usage() { echo " {{PORTS}} Replaced by the forwarded port numbers (comma separated)" echo " {{VPN_INTERFACE}} Replaced by the VPN interface name (e.g. tun0)" echo "" - echo "Example commands (set as value of VPN_PORT_FORWARDING_UP_COMMAND):" + echo "Examples:" echo "# With authentication:" - echo "/bin/sh -c \"/scripts/qbittorrent-port-update.sh --user ADMIN --pass **** --port {{PORT}} --iface {{VPN_INTERFACE}} --webui-port 8080\"" + echo "VPN_PORT_FORWARDING_UP_COMMAND=/bin/sh -c \"/scripts/qbittorrent-port-update.sh --user ADMIN --pass **** --port {{PORT}} --iface {{VPN_INTERFACE}} --webui-port 8080\"" + echo "VPN_PORT_FORWARDING_DOWN_COMMAND=/bin/sh -c \"/scripts/qbittorrent-port-update.sh --user ADMIN --pass **** --port 0 --iface lo --webui-port 8080\"" echo "# Without authentication (\"Bypass authentication for clients on localhost\" enabled in qBittorrent):" - echo "/bin/sh -c \"/scripts/qbittorrent-port-update.sh --port {{PORT}} --iface {{VPN_INTERFACE}} --webui-port 8080\"" + echo "VPN_PORT_FORWARDING_UP_COMMAND=/bin/sh -c \"/scripts/qbittorrent-port-update.sh --port {{PORT}} --iface {{VPN_INTERFACE}} --webui-port 8080\"" + echo "VPN_PORT_FORWARDING_DOWN_COMMAND=/bin/sh -c \"/scripts/qbittorrent-port-update.sh --port 0 --iface lo --webui-port 8080\"" } while [ $# -gt 0 ]; do From d7516a06eb38a6d028d37c1d03108413039428ab Mon Sep 17 00:00:00 2001 From: Quentin McGaw Date: Tue, 3 Mar 2026 17:48:17 +0000 Subject: [PATCH 10/13] remove documentation on gluetun templating variables since it's out of scope --- extras/scripts/qbittorrent-port-update.sh | 5 ----- 1 file changed, 5 deletions(-) diff --git a/extras/scripts/qbittorrent-port-update.sh b/extras/scripts/qbittorrent-port-update.sh index 8c2a7e000..7c4325f63 100644 --- a/extras/scripts/qbittorrent-port-update.sh +++ b/extras/scripts/qbittorrent-port-update.sh @@ -43,11 +43,6 @@ usage() { echo " --url URL Specify the qBittorrent API URL. Not compatible with --webui-port" echo " Default: \"${WEBUI_URL}\"" echo "" - echo "Gluetun Placeholders (available in VPN_PORT_FORWARDING_UP_COMMAND):" - echo " {{PORT}} Replaced by the forwarded port number (or first port if multiple)" - echo " {{PORTS}} Replaced by the forwarded port numbers (comma separated)" - echo " {{VPN_INTERFACE}} Replaced by the VPN interface name (e.g. tun0)" - echo "" echo "Examples:" echo "# With authentication:" echo "VPN_PORT_FORWARDING_UP_COMMAND=/bin/sh -c \"/scripts/qbittorrent-port-update.sh --user ADMIN --pass **** --port {{PORT}} --iface {{VPN_INTERFACE}} --webui-port 8080\"" From 48c69c57ea95424bd4d3690bc638c09ad9587368 Mon Sep 17 00:00:00 2001 From: Quentin McGaw Date: Tue, 3 Mar 2026 17:56:14 +0000 Subject: [PATCH 11/13] Move Copy after apk installs to reduce re-build times when rebuilding with script changes --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index d5adea104..9cf1b3b9c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -230,7 +230,6 @@ ENV VPN_SERVICE_PROVIDER=pia \ ENTRYPOINT ["/gluetun-entrypoint"] EXPOSE 8000/tcp 8888/tcp 8388/tcp 8388/udp HEALTHCHECK --interval=5s --timeout=5s --start-period=10s --retries=3 CMD /gluetun-entrypoint healthcheck -COPY extras/scripts/qbittorrent-port-update.sh /scripts/qbittorrent-port-update.sh ARG TARGETPLATFORM RUN apk add --no-cache --update -l wget && \ apk add --no-cache --update -X "https://dl-cdn.alpinelinux.org/alpine/v3.17/main" openvpn\~2.5 && \ @@ -241,4 +240,5 @@ RUN apk add --no-cache --update -l wget && \ rm -rf /var/cache/apk/* /etc/openvpn/*.sh /usr/lib/openvpn/plugins/openvpn-plugin-down-root.so && \ deluser openvpn && \ mkdir /gluetun +COPY extras/scripts/qbittorrent-port-update.sh /scripts/qbittorrent-port-update.sh COPY --from=build /tmp/gobuild/entrypoint /gluetun-entrypoint From e5cefb5f181f68a0f90bc0506c29d414297c28b0 Mon Sep 17 00:00:00 2001 From: Quentin McGaw Date: Sun, 29 Mar 2026 01:29:58 +0100 Subject: [PATCH 12/13] Check port is a valid port number Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- extras/scripts/qbittorrent-port-update.sh | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/extras/scripts/qbittorrent-port-update.sh b/extras/scripts/qbittorrent-port-update.sh index 7c4325f63..0c0c0c005 100644 --- a/extras/scripts/qbittorrent-port-update.sh +++ b/extras/scripts/qbittorrent-port-update.sh @@ -85,6 +85,18 @@ while [ $# -gt 0 ]; do exit 1 fi VPN_PORT=$(echo "$2" | cut -d',' -f1) + case "$VPN_PORT" in + ''|*[!0-9]*) + echo "Error: --port must be a numeric value between 0 and 65535." + usage + exit 1 + ;; + esac + if [ "$VPN_PORT" -lt 0 ] || [ "$VPN_PORT" -gt 65535 ]; then + echo "Error: --port must be between 0 and 65535." + usage + exit 1 + fi shift 2 ;; --iface) From 839efa7e1364e5452113317bfe55e6444f45a706 Mon Sep 17 00:00:00 2001 From: Quentin McGaw Date: Sun, 29 Mar 2026 01:36:10 +0100 Subject: [PATCH 13/13] Use a cookie jar file to avoid command injections Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- extras/scripts/qbittorrent-port-update.sh | 34 +++++++++++++++-------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/extras/scripts/qbittorrent-port-update.sh b/extras/scripts/qbittorrent-port-update.sh index 0c0c0c005..7f1d64567 100644 --- a/extras/scripts/qbittorrent-port-update.sh +++ b/extras/scripts/qbittorrent-port-update.sh @@ -160,36 +160,48 @@ if [ "${_USECRED}" = "true" ]; then exit 1 fi - cookie=$(wget ${WGET_OPTS} -qO- \ + COOKIE_JAR=$(mktemp) + if [ ! -w "${COOKIE_JAR}" ]; then + echo "ERROR: Failed to create temporary cookie jar" + exit 1 + fi + + wget ${WGET_OPTS} --save-cookies "${COOKIE_JAR}" --keep-session-cookies \ --header "Referer: ${WEBUI_URL}" \ --post-data "username=${USERNAME}&password=${PASSWORD}" \ - "${WEBUI_URL}/v2/auth/login" \ - --server-response 2>&1 | \ - grep -i "set-cookie:" | \ - sed 's/.*set-cookie: //I;s/;.*//') + "${WEBUI_URL}/v2/auth/login" -qO- >/dev/null 2>&1 - if [ -z "${cookie}" ]; then + if [ $? -ne 0 ] || [ ! -s "${COOKIE_JAR}" ]; then echo "ERROR: Failed to authenticate with qBittorrent. Check username/password or verify WebUI is accessible" + rm -f "${COOKIE_JAR}" exit 1 fi - - # set cookie for future requests - WGET_OPTS="${WGET_OPTS} --header=Cookie:$cookie" fi # update qBittorrent preferences via API, the first call disabled everything and sets safe defaults # This is required as per https://github.com/qdm12/gluetun-wiki/pull/147 and https://github.com/qdm12/gluetun/issues/2997#issuecomment-3566749335 -wget ${WGET_OPTS} -qO- --post-data="json={\"random_port\":false,\"upnp\":false,\"listen_port\":0,\"current_network_interface\":\"lo\",\"current_interface_address\":\"127.0.0.1\"}" "$WEBUI_URL/v2/app/setPreferences" +if [ "${_USECRED}" = "true" ]; then + wget ${WGET_OPTS} --load-cookies "${COOKIE_JAR}" -qO- --post-data="json={\"random_port\":false,\"upnp\":false,\"listen_port\":0,\"current_network_interface\":\"lo\",\"current_interface_address\":\"127.0.0.1\"}" "$WEBUI_URL/v2/app/setPreferences" +else + wget ${WGET_OPTS} -qO- --post-data="json={\"random_port\":false,\"upnp\":false,\"listen_port\":0,\"current_network_interface\":\"lo\",\"current_interface_address\":\"127.0.0.1\"}" "$WEBUI_URL/v2/app/setPreferences" +fi if [ $? -ne 0 ]; then echo "ERROR: Failed to reset qBittorrent settings" + [ "${_USECRED}" = "true" ] && rm -f "${COOKIE_JAR}" exit 1 fi # second call to set the actual port, interface and address -wget ${WGET_OPTS} -qO- --post-data="json={\"listen_port\":$VPN_PORT,\"current_network_interface\":\"$VPN_INTERFACE\",\"current_interface_address\":\"$VPN_ADDRESS\"}" "$WEBUI_URL/v2/app/setPreferences" +if [ "${_USECRED}" = "true" ]; then + wget ${WGET_OPTS} --load-cookies "${COOKIE_JAR}" -qO- --post-data="json={\"listen_port\":$VPN_PORT,\"current_network_interface\":\"$VPN_INTERFACE\",\"current_interface_address\":\"$VPN_ADDRESS\"}" "$WEBUI_URL/v2/app/setPreferences" +else + wget ${WGET_OPTS} -qO- --post-data="json={\"listen_port\":$VPN_PORT,\"current_network_interface\":\"$VPN_INTERFACE\",\"current_interface_address\":\"$VPN_ADDRESS\"}" "$WEBUI_URL/v2/app/setPreferences" +fi if [ $? -ne 0 ]; then echo "ERROR: Failed to apply qBittorrent port/interface settings" + [ "${_USECRED}" = "true" ] && rm -f "${COOKIE_JAR}" exit 1 fi +[ "${_USECRED}" = "true" ] && rm -f "${COOKIE_JAR}" echo "qBittorrent updated to use peer-port: ${VPN_PORT}, interface: \"${VPN_INTERFACE}\", address: \"${VPN_ADDRESS}\""