Skip to content

Instantly share code, notes, and snippets.

@Benricheson101
Last active February 21, 2024 03:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Benricheson101/d4e8ca27c648878984da88359ddea88a to your computer and use it in GitHub Desktop.
Save Benricheson101/d4e8ca27c648878984da88359ddea88a to your computer and use it in GitHub Desktop.
Bash script to automatically update qBittorrent port with a forwarded port from Gluetun
# Make sure to bind mount the docker socket so docker commands work inside the container:
# -v /var/run/docker.sock:/var/run/docker.sock
FROM docker
RUN apk add bash curl perl
COPY update_port.sh .
ENTRYPOINT ["/update_port.sh"]
#!/usr/bin/env bash
QBT_USERNAME=${QBT_USERNAME:-admin}
QBT_PASSWORD=${QBT_PASSWORD:-adminadmin}
QBT_URL=${QBT_URL:-http://localhost:8080}
GLUETUN_URL=${GLUETUN_URL:-http://localhost:8000}
LOG_LEVEL=${LOG_LEVEL:-info}
debug() {
[[ $LOG_LEVEL == "debug" ]] && echo $@
}
log() {
echo $@
}
err() {
echo $@ >&2
}
COOKIE_JAR=$(mktemp)
login() {
debug "logging in"
body=$(curl -s "${QBT_URL}/api/v2/auth/login" \
--fail-with-body \
-c $COOKIE_JAR \
--data-urlencode username=${QBT_USERNAME} \
--data-urlencode password=${QBT_PASSWORD}
)
code=$?
[[ $body == "Fails." ]] && return 1
return $code
}
logout() {
debug "logging out"
curl -s "${QBT_URL}/api/v2/auth/logout" \
-X POST \
-b $COOKIE_JAR \
-c $COOKIE_JAR \
> /dev/null
}
isnumber() {
[[ $1 =~ ^[0-9]+$ ]]
}
get_gluetun_port() {
if ! port=$(curl -s --fail "${GLUETUN_URL}/v1/openvpn/portforwarded" | perl -ne '/"port":\s?(\d+)/ && print $1') ; then
err "failed to read gluetun forwarded port"
return 1
fi
if ! isnumber $port || ((port < 1024)) ; then
err "port is not numeric or below 1024: $port"
return 1
fi
echo -n $port
return 0
}
get_current_qbt_port() {
curl -s "${QBT_URL}/api/v2/app/preferences" \
--fail-with-body \
-b $COOKIE_JAR \
-c $COOKIE_JAR \
| perl -ne '/"listen_port":\s?(\d+),/ && print $1'
}
set_qbt_port() {
body=$(printf '{"listen_port": %d}' "$1")
curl "${QBT_URL}/api/v2/app/setPreferences" \
--fail-with-body \
-s \
-X POST \
-b $COOKIE_JAR \
-c $COOKIE_JAR \
--data-urlencode "json=${body}"
}
cleanup() {
debug "EXIT: Running cleanup"
logout
rm -f $COOKIE_JAR
pkill -P $$
}
trap cleanup SIGINT EXIT
show_help() {
cat - <<EOF | fold -sw100
Usage: $0 [options]
-h show help
-i interval automatically run at an interval (seconds)
-c container_name listen for Docker container \`start\` events of \`container_name\`
Note: options \`-i\` and \`-c\` can be used together to auto run on container start/restart, and run periodically. This is useful if Gluetun's internal healthcheck restarts the VPN (thereby rerolling the forwarded port) without restarting the container.
EOF
}
run() {
if ! login ; then
code=$?
err "failed to login: $code"
return $code
fi
debug "logged in"
gluetun_port=$(get_gluetun_port)
if [[ $? != 0 ]] ; then
err "failed to get forwarded gluetun port"
return 1
fi
debug "got gluetun port: $gluetun_port"
if ! isnumber $gluetun_port ; then
err "gluetun port is not a number"
return 1
fi
if ! curr_qbt_port=$(get_current_qbt_port) ; then
err "failed to read current qBittorrent port"
return 1
fi
debug "got current qbittorrent port: $curr_qbt_port"
if isnumber $curr_qbt_port && [[ $curr_qbt_port == $gluetun_port ]] ; then
debug "ports match $curr_qbt_port $gluetun_port"
return 0
fi
log "setting port to $gluetun_port"
set_qbt_port $gluetun_port
logout
}
run_interval() {
interval=$1
while : ; do
run
sleep $interval &
wait $!
done
}
run_watch_docker() {
container_name=$1
last=0
while read type time container_name ; do
# skip if it runs in quick succession. this could happen if the container gets into a crahsk loop
if ((time - last < 30 )); then
debug "Got \`$type\` less than 30 seconds after the previous. Skipping..."
continue;
fi
last=$time
log "Got \`$type\` for \`$container_name\`. Waiting 20 seconds before running"
# wait 10 seconds so it can connect
sleep 20
run
done < <(docker events --filter event=start --filter "container=${container_name}" --format "{{.Type}} {{.Time}} {{index .Actor.Attributes \"name\"}}")
}
trap run SIGHUP
if [[ $# == 0 ]] ; then
run
exit 0
fi
while getopts "hi:c:" arg ; do
case $arg in
h)
show_help
;;
i)
log "Running every $OPTARG seconds"
(run_interval "$OPTARG") &
;;
c)
log "Listening to Docker events of container: $OPTARG"
(run_watch_docker "$OPTARG") &
;;
*)
;;
esac
done
shift $(($OPTIND -1))
[[ $LOG_LEVEL == "debug" ]] && jobs
wait -n
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment