├── .gitignore ├── entrypoint.sh ├── Dockerfile ├── .github └── workflows │ ├── dockerhub-description.yml │ ├── build-and-push-image.yml │ └── release-image.yml ├── main.sh └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | config/ 2 | -------------------------------------------------------------------------------- /entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | ./main.sh 5 | /usr/sbin/crond -f -l 8 6 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:latest 2 | 3 | WORKDIR /usr/src/app 4 | 5 | VOLUME [ "/config" ] 6 | 7 | RUN apk --no-cache add jq curl 8 | 9 | RUN echo "*/10 * * * * /bin/sh /usr/src/app/main.sh" | crontab - 10 | 11 | COPY *.sh ./ 12 | 13 | CMD ["/usr/src/app/entrypoint.sh"] 14 | -------------------------------------------------------------------------------- /.github/workflows/dockerhub-description.yml: -------------------------------------------------------------------------------- 1 | 2 | name: Update Docker Hub Description 3 | on: 4 | push: 5 | branches: 6 | - master 7 | paths: 8 | - README.md 9 | - .github/workflows/dockerhub-description.yml 10 | workflow_dispatch: 11 | 12 | jobs: 13 | dockerHubDescription: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v2 17 | 18 | - name: Docker Hub Description 19 | uses: peter-evans/dockerhub-description@v2 20 | with: 21 | username: mjmeli 22 | password: ${{ secrets.DOCKERHUB_TOKEN }} 23 | repository: mjmeli/${{ github.event.repository.name }} 24 | short-description: ${{ github.event.repository.description }} 25 | readme-filepath: ./README.md -------------------------------------------------------------------------------- /.github/workflows/build-and-push-image.yml: -------------------------------------------------------------------------------- 1 | name: Build and push image 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | workflow_dispatch: 8 | 9 | permissions: 10 | packages: write 11 | 12 | jobs: 13 | build_and_push_image: 14 | name: Build Docker image and push to registries 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v2 19 | 20 | - name: Set up Docker Buildx 21 | id: buildx 22 | uses: docker/setup-buildx-action@v1 23 | 24 | - name: Login to DockerHub 25 | uses: docker/login-action@v1 26 | with: 27 | username: mjmeli 28 | password: ${{ secrets.DOCKERHUB_TOKEN }} 29 | 30 | - name: Login to GitHub Container Registry 31 | uses: docker/login-action@v1 32 | with: 33 | registry: ghcr.io 34 | username: ${{ github.repository_owner }} 35 | password: ${{ secrets.GITHUB_TOKEN }} 36 | 37 | - name: Build and push 38 | id: docker_build_push 39 | uses: docker/build-push-action@v2 40 | with: 41 | push: true 42 | tags: | 43 | mjmeli/${{ github.event.repository.name }}:latest 44 | mjmeli/${{ github.event.repository.name }}:${{ github.sha }} 45 | ghcr.io/${{ github.repository }}:latest 46 | ghcr.io/${{ github.repository }}:${{ github.sha }} 47 | 48 | - name: Image digest 49 | run: echo ${{ steps.docker_build_push.outputs.digest }} -------------------------------------------------------------------------------- /main.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | qbt_username="${QBT_USERNAME:-admin}" 5 | qbt_password="${QBT_PASSWORD:-adminadmin}" 6 | qbt_addr="${QBT_ADDR:-http://localhost:8080}" # ex. http://10.0.1.48:8080 7 | gtn_addr="${GTN_ADDR:-http://localhost:8000}" # ex. http://10.0.1.48:8000 8 | 9 | if [[ -n "$GTN_USERNAME" && -n "$GTN_PASSWORD" ]]; then 10 | echo "Attempting to retrieve port from Gluetun via username and password..." 11 | port_number=$(curl --fail --silent --show-error --user "$GTN_USERNAME:$GTN_PASSWORD" $gtn_addr/v1/portforward | jq '.port') 12 | elif [ -n "$GTN_APIKEY" ]; then 13 | echo "Attempting to retrieve port from Gluetun via api key..." 14 | port_number=$(curl --fail --silent --show-error --header "X-API-Key: $GTN_APIKEY" $gtn_addr/v1/portforward | jq '.port') 15 | else 16 | echo "Attempting to retrieve port from Gluetun without authentication..." 17 | port_number=$(curl --fail --silent --show-error $gtn_addr/v1/portforward | jq '.port') 18 | fi 19 | 20 | if [ ! "$port_number" ] || [ "$port_number" = "0" ]; then 21 | echo "Could not get current forwarded port from gluetun, exiting..." 22 | exit 1 23 | else 24 | echo "Port number succesfully retrieved from Gluetun: $port_number" 25 | fi 26 | 27 | curl --fail --silent --show-error --cookie-jar /tmp/cookies.txt --cookie /tmp/cookies.txt --header "Referer: $qbt_addr" --data "username=$qbt_username" --data "password=$qbt_password" $qbt_addr/api/v2/auth/login 1> /dev/null 28 | 29 | listen_port=$(curl --fail --silent --show-error --cookie-jar /tmp/cookies.txt --cookie /tmp/cookies.txt $qbt_addr/api/v2/app/preferences | jq '.listen_port') 30 | 31 | if [ ! "$listen_port" ]; then 32 | echo "Could not get current listen port, exiting..." 33 | exit 1 34 | fi 35 | 36 | if [ "$port_number" = "$listen_port" ]; then 37 | echo "Port already set, exiting..." 38 | exit 0 39 | fi 40 | 41 | echo "Updating port to $port_number" 42 | 43 | curl --fail --silent --show-error --cookie-jar /tmp/cookies.txt --cookie /tmp/cookies.txt --data-urlencode "json={\"listen_port\": $port_number}" $qbt_addr/api/v2/app/setPreferences 44 | 45 | echo "Successfully updated port" 46 | -------------------------------------------------------------------------------- /.github/workflows/release-image.yml: -------------------------------------------------------------------------------- 1 | name: Build and push release image 2 | 3 | on: 4 | release: 5 | types: [published] 6 | # workflow_dispatch allows manual triggering to backfill Docker images for existing releases 7 | # Go to Actions > Build and push release image > Run workflow, then enter the tag name 8 | workflow_dispatch: 9 | inputs: 10 | tag_name: 11 | description: 'Release tag name (e.g., v1.0.0)' 12 | required: true 13 | type: string 14 | 15 | permissions: 16 | packages: write 17 | 18 | jobs: 19 | build_and_push_release_image: 20 | name: Build Docker image and push to registries 21 | runs-on: ubuntu-latest 22 | steps: 23 | # Determine tag name: use release tag when triggered by release event, 24 | # otherwise use the tag_name input from workflow_dispatch (for backfilling) 25 | - name: Set tag name 26 | id: set_tag 27 | run: | 28 | if [ "${{ github.event_name }}" = "release" ]; then 29 | echo "tag=${{ github.event.release.tag_name }}" >> $GITHUB_OUTPUT 30 | else 31 | echo "tag=${{ inputs.tag_name }}" >> $GITHUB_OUTPUT 32 | fi 33 | 34 | - name: Checkout 35 | uses: actions/checkout@v2 36 | with: 37 | # Checkout the release tag commit 38 | ref: ${{ steps.set_tag.outputs.tag }} 39 | 40 | - name: Set up Docker Buildx 41 | id: buildx 42 | uses: docker/setup-buildx-action@v1 43 | 44 | - name: Login to DockerHub 45 | uses: docker/login-action@v1 46 | with: 47 | username: mjmeli 48 | password: ${{ secrets.DOCKERHUB_TOKEN }} 49 | 50 | - name: Login to GitHub Container Registry 51 | uses: docker/login-action@v1 52 | with: 53 | registry: ghcr.io 54 | username: ${{ github.repository_owner }} 55 | password: ${{ secrets.GITHUB_TOKEN }} 56 | 57 | - name: Build and push 58 | id: docker_build_push 59 | uses: docker/build-push-action@v2 60 | with: 61 | push: true 62 | tags: | 63 | mjmeli/${{ github.event.repository.name }}:${{ steps.set_tag.outputs.tag }} 64 | ghcr.io/${{ github.repository }}:${{ steps.set_tag.outputs.tag }} 65 | 66 | - name: Image digest 67 | run: echo ${{ steps.docker_build_push.outputs.digest }} 68 | 69 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # qbittorrent-port-forward-gluetun-server 2 | 3 | A shell script and Docker container for automatically setting qBittorrent's listening port from Gluetun's control server. 4 | 5 | ## Config 6 | 7 | ### Environment Variables 8 | 9 | | Variable | Example | Default | Description | 10 | |--------------|-----------------------------|-------------------------|--------------------------------------------------------------------------------| 11 | | QBT_USERNAME | `username` | `admin` | qBittorrent username | 12 | | QBT_PASSWORD | `password` | `adminadmin` | qBittorrent password | 13 | | QBT_ADDR | `http://192.168.1.100:8080` | `http://localhost:8080` | HTTP URL for the qBittorrent web UI, with port | 14 | | GTN_ADDR | `http://192.168.1.100:8000` | `http://localhost:8000` | HTTP URL for the gluetun control server, with port | 15 | | GTN_USERNAME | `username` | *None* | Username for authentication to gluetun control server (if basic auth enabled) | 16 | | GTN_PASSWORD | `password` | *None* | Password for authentication to gluetun control server (if basic auth enabled) | 17 | | GTN_APIKEY | `apikey` | *None* | API Key for authentication to gluetun control server (if API key auth enabled) | 18 | 19 | 20 | ## Gluetun Control Server Authentication 21 | Starting in Gluetun v3.4, it is required to setup authentication on the Gluetun control server routes. 22 | 23 | See this link for information on how to set this up: https://github.com/qdm12/gluetun-wiki/blob/main/setup/advanced/control-server.md#authentication 24 | 25 | Once configured in Gluetun, you can configure this container to use the appropriate authentication method: 26 | - If using `none` auth, you do not need to provide any of the authentication environment variables 27 | - If using `basic` auth, you should set the `GTN_USERNAME` and `GTN_PASSWORD` environment variables 28 | - If using `apikey` auth, you should set the `GTN_APIKEY` environment variable 29 | 30 | ## Example 31 | 32 | ### Docker-Compose 33 | 34 | The following is an example docker-compose: 35 | 36 | ```yaml 37 | qbittorrent-port-forward-gluetun-server: 38 | image: mjmeli/qbittorrent-port-forward-gluetun-server 39 | container_name: qbittorrent-port-forward-gluetun-server 40 | restart: unless-stopped 41 | environment: 42 | - QBT_USERNAME=username 43 | - QBT_PASSWORD=password 44 | - QBT_ADDR=http://192.168.1.100:8080 45 | - GTN_ADDR=http://192.168.1.100:8000 46 | - GTN_APIKEY=CHANGEME 47 | ``` 48 | 49 | ## Development 50 | 51 | ### Build Image 52 | ```bash 53 | docker build . -t qbittorrent-port-forward-gluetun-server 54 | ``` 55 | 56 | ### Run Container 57 | ```bash 58 | docker run --rm -it -e QBT_USERNAME=admin -e QBT_PASSWORD=adminadmin -e QBT_ADDR=http://192.168.1.100:8080 -e GTN_ADDR=http://192.168.1.100:8000 -e GTN_APIKEY=CHANGEME qbittorrent-port-forward-gluetun-server:latest 59 | ``` 60 | --------------------------------------------------------------------------------