qdm12 / gluetun

VPN client in a thin Docker container for multiple VPN providers, written in Go, and using OpenVPN or Wireguard, DNS over TLS, with a few proxy servers built-in.
https://hub.docker.com/r/qmcgaw/gluetun
MIT License
6.95k stars 339 forks source link

Feature request: Enable updating QBittorrent for dynamic Port Forwarding #1751

Open tyvsmith opened 1 year ago

tyvsmith commented 1 year ago

What's the feature 🧐

ProtonVPN and potentially other torrent clients assign a dynamically generated port on each connection, requiring the client to be updated.

There are several approaches on the internet to keep these in sync by running a cron to check for the port on the VPN container and hit the QBittorrent API. This approach seems pretty hacky and not reliable.

It would be preferred if there was a standardized set of hooks that could be utilized to update the clients with a new port, or even first-class integration.

Extra information and references

Example Sync Script for TrueCharts/TrueNAS using the QBIttorrent app which sidecars Gluetun

nigiriemoji commented 11 months ago

Try out charlocharlie/qbittorrent-port-forward-file:latest

ceramicwhite commented 10 months ago

I had to make a few changes to charlocharlie/qbittorrent-port-forward-file to get it to work but my setup is working flawless now. Here's my compose for anyone else who stumbles on this looking for an answer:

services:
  port-forward:
    image: ceramicwhite/gluetun-port-forward-qbit-cron:latest
    container_name: port-forward
    restart: on-failure
    environment:
      - QBT_USERNAME=admin
      - QBT_PASSWORD=adminadmin
      - QBT_ADDR=http://gluetun:8080
      - GTN_ADDR=http://gluetun:8000
    depends_on:
      - gluetun
      - qbittorrent

  gluetun:
    container_name: gluetun
    image: qmcgaw/gluetun
    stop_grace_period: 1m
    cap_add:
      - net_admin
    devices:
      - /dev/net/tun:/dev/net/tun
    environment:
    - VPN_SERVICE_PROVIDER=custom
    - VPN_PORT_FORWARDING_PROVIDER=<your port forwarding provider>
    - VPN_PORT_FORWARDING=on
    - VPN_TYPE=wireguard
    - WIREGUARD_PRIVATE_KEY=<your private key>
    - WIREGUARD_PUBLIC_KEY=<your public key>
    - WIREGUARD_ADDRESSES=<your wireguard address>
    - VPN_ENDPOINT_IP=<your vpn endpoint ip>
    - VPN_ENDPOINT_PORT=<your vpn endpoint port>
    ports:
      - 6380:6380
      - 4443:4443
      - 8888:8888/tcp # HTTP proxy
      - 8388:8388/tcp # Shadowsocks
      - 8388:8388/udp # Shadowsocks
      - 8080:8080     #qBittorrent
    restart: unless-stopped
    volumes:
      - ./data/gluetun:/tmp/gluetun

  qbittorrent:
    image: linuxserver/qbittorrent:latest
    container_name: qbittorrent
    stop_grace_period: 1m
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=America/Los_Angeles
      - WEBUI_PORT=8080
    volumes:
      - ./data/qbittorrent/config:/config
      - ./data/qbittorrent/downloads:/downloads
    restart: unless-stopped
    network_mode: "service:gluetun"
    depends_on:
      - gluetun

  # swag:
  #   image: lscr.io/linuxserver/swag:latest
  #   container_name: swag
  #   stop_grace_period: 1m
  #   cap_add:
  #     - NET_ADMIN
  #   environment:
  #     - PUID=1000
  #     - PGID=1000
  #     - TZ=America/Los_Angeles
  #     - URL=
  #     - VALIDATION=dns
  #     - SUBDOMAINS=wildcard
  #     # CERTPROVIDER= #optional
  #     - DNSPLUGIN=cloudflare
  #     #- PROPAGATION= #optional
  #     - EMAIL=
  #     #- ONLY_SUBDOMAINS=false #optional
  #     - EXTRA_DOMAINS=
  #     - STAGING=false #optional
  #     - DOCKER_MODS=linuxserver/mods:swag-auto-reload
  #   volumes:
  #     - ./data/swag/config:/config
  #   ports:
  #     - 443:443
  #     - 80:80
  #   restart: unless-stopped

networks:
    default:
      name: swag_network
      ipam:
          driver: default
Craylum commented 10 months ago

@ceramicwhite I have tried your workaround on my system. The container starts but the port does not update on qbittorrent. The output of the container reads [FATAL tini (8)] exec /app/entrypoint.sh failed: Exec format error. For some extra info, I have started the stack through portainer and the host is a raspberry pi 4 running dietpi

mjmeli commented 10 months ago

I recently forked claabs/qbittorrent-port-forward-file to build a container that would retrieve gluetun's port via the control server instead of the forwarded_port file: mjmeli/qbittorrent-port-forward-gluetun-server. According to gluetun documentation, the forwarded port file will be deprecated in the v4.0.0 release. Perhaps you can give this a try, it has been working for me flawlessly. There is a sample docker-compose in the README.

afiestas commented 10 months ago

I wrote a quick app to synchronize port changes with other services https://github.com/afiestas/gluetun-sync

if @qdm12 is interested I could try to merge the code as a new "loop" within gluetun.

FrenchGithubUser commented 9 months ago

+1 and subscribing to this issue

also @mjmeli , by using your docker-compose sample in the same docker-compose file as qbittorrent and gluetun, I get the requested url returned error: 401 could not get current forwarded port from gluetun, exiting... and when visiting gluetun's url, I have a prompt for a login/password and don't know what those creds could be, nor if I should specify them in your container

mjmeli commented 9 months ago

@FrenchGithubUser AFAIK, the control server does not have any authentication. I would double check you are using the correct port, for example you should be able to hit http://<gluetun IP>:<gluetun port>/v1/publicip/ip. I don't see anything in the documentation about it having authentication either.

FrenchGithubUser commented 9 months ago

@mjmeli nvm, the variable HTTP_CONTROL_SERVER_ADDRESS was not set and the relevant port not forwarded in the docker-compose file

ar-jan commented 8 months ago

I'm confused, how are people successfully using dynamically allocated port forwarding? In the typical setup (e.g. a gluetun service and a qbittorrent service which uses the gluetun network, you would need not just to update the forwarded port in qbittorrent, but also expose that port in the gluetun service. But you don't know the port until the container has started, and it isn't possible to update the port mapping of a running Docker container. So the only way seems to be to have gluetun running inside the same container as your application?

ceramicwhite commented 8 months ago

@FrenchGithubUser

The port forwarding is handled through the VPN tunnel itself, and the process inside the container can adjust to use whatever port has been assigned by the VPN service provider. This doesn't require a change to the Docker container's port mappings, because the port that's being forwarded is not directly exposed on the host; instead, it's routed through the VPN connection, which Gluetun manages.

FrenchGithubUser commented 8 months ago

I ended up using another container that was made for gluetun and qbittorrent

KonRay commented 7 months ago

+1 for this to be implemented as a native gluetun feature

schumi4 commented 6 months ago

Any updates on this? Would be a nice feature to have natively.

kristof-mattei commented 3 months ago

Here's my hands-off solution:

I'm Ansible, but this here uses no Ansible features, so it should easy to create a docker-compose from it:

GlueTun

    - name: Create vpn container
      community.general.docker_container:
        container_default_behavior: "no_defaults"
        name: vpn
        image: qmcgaw/gluetun:latest
        restart_policy: always
        capabilities:
          - NET_ADMIN # Required
        devices:
          - /dev/net/tun # not sure why required, works without, but it's in the instructions
        env:
          # ...
          VPN_PORT_FORWARDING: "on"
        mounts:
          - type: bind
            source: /containers/gluetun/tmp/gluetun
            target: /tmp/gluetun
        labels:
          "traefik.enable": "true"
          # ...
        ports: []
        networks:
          - name: bridge

qBittorrent

    - name: Create qbittorrent container
      community.general.docker_container:
        container_default_behavior: "no_defaults"
        name: qbittorrent
        image: ghcr.io/linuxserver/qbittorrent:latest
        restart_policy: always
        network_mode: container:vpn
        env:
          # ...
        mounts:
          - type: bind
            source: /containers/qbittorrent/config
            target: /config
          # this mounts the gluetun output into our container
          - type: bind
            source: /containers/gluetun/tmp/gluetun
            target: /tmp/gluetun
          - type: bind
            source: /containers/qbittorrent/custom-services.d
            target: /custom-services.d
          - type: bind
            source: /downloads
            target: /downloads

Custom qBittorren service

The following script sits in /containers/qbittorrent/custom-services.d/update-forwarded-port.sh. It is responsible for reading the forwarded_port every DEFAULT_SLEEP and update qBittorrent if it changed. It asks the port every DEFAULT_SLEEP and if it changed, sends the update. We could blindly invoke the change port API every time, but I'm not sure how qBittorrent handles it if they don't change.

#!/usr/bin/with-contenv bash

# Change me
FILE=/tmp/gluetun/forwarded_port
# every minute
DEFAULT_SLEEP=60

while :; do
    SLEEP=$DEFAULT_SLEEP

    PORT=$(curl --silent --request GET --url "http://localhost:8080/api/v2/app/preferences" | jq ".listen_port")
    NEW_PORT=$(cat $FILE)

    if [[ "" == "$NEW_PORT" ]]; then
        echo "No port in file or file not found?"
        # retry faster
        SLEEP=10
    elif [[ "" == "$PORT" ]]; then
        echo "API did not respond?"
        # retry faster
        SLEEP=10
    elif [[ "$PORT" == "$NEW_PORT" ]]; then
        echo "Ports did not change"
    else
        echo "Ports changed: old: $PORT, new: $NEW_PORT"
        echo "Trying to update port via API"
        curl \
            --silent \
            --request POST \
            --url "http://localhost:8080/api/v2/app/setPreferences" \
            --data "json={\"listen_port\": $NEW_PORT}"

        if [[ $? -eq 0 ]]; then
            echo "Port updated successfully"
            PORT=$NEW_PORT
        else
            echo "Error updating port"
        fi

    fi

    echo "Sleeping for $SLEEP, retrying afterwards"
    sleep $SLEEP
done

Additional config

Make sure qBittorrent allows for unauthenticated API calls from localhost, or add in your own auth in the curl call.

image

t-anc commented 3 months ago

For people who use Linuxserver's qbittorent image, i've made a docker mod to do just that : https://github.com/t-anc/GSP-Qbittorent-Gluetun-sync-port-mod

Just read the disclaimer in the README ;)

arsenicks commented 1 month ago

For people who use Linuxserver's qbittorent image, i've made a docker mod to do just that : https://github.com/t-anc/GSP-Qbittorent-Gluetun-sync-port-mod

Just read the disclaimer in the README ;)

Thanks for this, it's the cleanest way of doing it and work perfectly.. Good job!