this post was submitted on 14 Dec 2025
51 points (98.1% liked)

Programming

26036 readers
164 users here now

Welcome to the main community in programming.dev! Feel free to post anything relating to programming here!

Cross posting is strongly encouraged in the instance. If you feel your post or another person's post makes sense in another community cross post into it.

Hope you enjoy the instance!

Rules

Rules

  • Follow the programming.dev instance rules
  • Keep content related to programming in some way
  • If you're posting long videos try to add in some form of tldr for those who don't want to watch videos

Wormhole

Follow the wormhole through a path of communities !webdev@programming.dev



founded 2 years ago
MODERATORS
 

So I'm learning python (by doing, reading, and doing some more), and I've been wanting to automate updating the listening port in my qbittorrent docker container when the gluetun vpn container changes the forwarded port.

This is what I came up with, which I combined with an hourly cronjob to keep it the listen port updated.

(Code under the spoiler tag to make this more readable).

Tap for spoiler

import re
import os
import logging
from datetime import datetime
from dotenv import load_dotenv

import docker
from qbittorrent import Client

# FUNCTION DECLARATION #


def log(code, message):
    logFilePath = "/opt/pyprojects/linux-iso-torrents/pf.log"
    logDateTime = datetime.today().strftime("%Y-%m-%d %H:%M:%S")
    message = f"[{logDateTime}] {message}"
    logger = logging.getLogger(__name__)
    logging.basicConfig(
        filename=f"{logFilePath}", encoding="utf-8", level=logging.DEBUG
    )
    match code:
        case "debug":
            logger.debug(message)
        case "info":
            logger.info(message)
        case "warning":
            logger.warning(message)
        case "error":
            logger.error(message)


def get_current_forwarded_port():
    client = docker.from_env()
    container = client.containers.get("a40124291102d")
    logs = container.logs().decode("utf-8")
    allPortForwards = re.findall(r".+port forwarded is [0-9]{5}", logs)
    currentForwardedPort = allPortForwards[-1].split(" ")[-1]

    return currentForwardedPort


def get_current_listening_port():
    qbConfigUrl = "/home/server/docker/qbit/config/qBittorrent/qBittorrent.conf"
    with open(qbConfigUrl) as f:
        config = f.read()
        qbitListenPort = re.search(r"Session\\Port=[0-9]{5}", config)
        currentListeningPort = qbitListenPort.group(0).split("=")[-1]

    return currentListeningPort


def update_qbittorrent_listen_port(port):
    QB_URL = os.getenv("QB_URL")
    QB_USER = os.getenv("QB_USER")
    QB_PASSWORD = os.getenv("QB_PASSWORD")
    portJSON = {}
    portJSON["listen_port"] = port
    qb = Client(QB_URL)
    qb.login(f"{QB_USER}", f"{QB_PASSWORD}")

    qb.set_preferences(**portJSON)


# BEGIN SCRIPT #

load_dotenv()

currentForwardedPort = get_current_forwarded_port()
currentListeningPort = get_current_listening_port()

if currentForwardedPort != currentListeningPort:
    update_qbittorrent_listen_port(currentPort)
    log("info", f"qbittorrent listen port set to {currentForwardedPort}")
else:
    log("info", "forwarded port and listen port are a match")

There's more I want to do, the next thing being to check the status of both containers and if one or both are down, to log that and gracefully exit, but for now, I'm pretty happy with this (open to any feedback, always willing to learn).

you are viewing a single comment's thread
view the rest of the comments
[โ€“] SinTan1729@programming.dev 4 points 2 months ago* (last edited 2 months ago) (1 children)

Good work, but this can be done in a more efficient way by utilizing the qBittorrent API in more places. Also, you may wanna utilize gluetun's VPN_PORT_FORWARDING_UP_COMMAND for calling the script.

Here's my script. I used bash since the gluetun container doesn't have Python in it.

Code

#!/bin/sh

# Adapted from https://github.com/claabs/qbittorrent-port-forward-file/blob/master/main.sh

# set -e

qbt_username="${QBT_USERNAME}"
qbt_password="${QBT_PASSWORD}"
qbt_addr="${QBT_ADDR:-http://localhost:8085/}"

if [ -z ${qbt_username} ]; then
    echo "You need to provide a username by the QBT_USERNAME env variable"
    exit 1
fi

if [ -z ${qbt_password} ]; then
    echo "You need to provide a password by the QBT_PASSWORD env variable"
    exit 1
fi

port_number="$1"
if [ -z "$port_number" ]; then
    port_number=$(cat /tmp/gluetun/forwarded_port)
fi

if [ -z "$port_number" ]; then
    echo "Could not figure out which port to set."
    exit 1
fi

wait_time=1
tries=0
while [ $tries -lt 10 ]; do
    wget --save-cookies=/tmp/cookies.txt --keep-session-cookies --header="Referer: $qbt_addr" --header="Content-Type: application/x-www-form-urlencoded" \
      --post-data="username=$qbt_username&password=$qbt_password" --output-document /dev/null --quiet "$qbt_addr/api/v2/auth/login"

    listen_port=$(wget --load-cookies=/tmp/cookies.txt --output-document - --quiet "$qbt_addr/api/v2/app/preferences" | grep -Eo '"listen_port":[0-9]+' | awk -F: '{print $2}')

    if [ ! "$listen_port" ]; then
        [ $wait_time -eq 1 ] && second_word="second" || second_word="seconds"
        echo "Could not get current listen port, trying again after $wait_time $second_word..."
        sleep $wait_time
        [ $wait_time -lt 32 ] && wait_time=$(( wait_time*2 )) # Set a max wait time of 32 secs
        tries=$(( tries+1 ))
        continue
    fi

    if [ "$port_number" = "$listen_port" ]; then
        echo "Port already set to $port_number, exiting..."
        exit 0
    fi

    echo "Updating port to $port_number"

    wget --load-cookies=/tmp/cookies.txt --header="Content-Type: application/x-www-form-urlencoded" --post-data='json={"listen_port": "'$port_number'"}' \
      --output-document /dev/null --quiet "$qbt_addr/api/v2/app/setPreferences"

    echo "Successfully updated port"
    exit 0
done

echo "Failed after 10 attempts!"
exit 2

For the auto-exit stuff, you may wanna check out docker's healthcheck functionality.

Not trying to put you down or anything here, it's great to learn to do things by yourself. Just giving you some pointers.

[โ€“] harsh3466@lemmy.ml 2 points 2 months ago

Thank you for the pointers! Didn't take it as a put down at all. I appreciate the feedback, and your bash script.

I didn't realize that gluetun had added this functionality, so I'll likely implement that as the more efficient way to keep the port updated.