Last active
February 21, 2024 03:41
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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