qBittorrent
qBittorrent is my chosen client for Bittorrent.
Installation⚑
Use binhex Docker
- Enable the announcement to all trackers
- Check the Advanced configurations
- I've tried the different Web UIs but none was of my licking.
- I've configured unpackerr to unpack the compressed downloads.
Migration from other client⚑
- First install the service
- Make sure that the default download directory has all the downloaded data to import.
- Then move all the
*.torrent
files from the old client to the torrent watch directory.
Python interaction⚑
Use this library, you can see some examples here.
Monitorization⚑
Metrics monitorisation⚑
There's this nice prometheus exporter which was rewritten in Go by martabal with it's graphana dashboard. With the information shown in the graphana dashboard it looks you can do alerts on whatever you want.
There is also another exporter written in Python but it has less release frequency and it looks like it has less metrics.
Installation⚑
Add to your docker compose the exporter docker:
qbittorrent-exporter:
image: ghcr.io/martabal/qbittorrent-exporter:latest
container_name: qbittorrent-exporter
environment:
- QBITTORRENT_BASE_URL=http://192.168.1.10:8080
- QBITTORRENT_PASSWORD='<your_password>'
- QBITTORRENT_USERNAME=admin
# ports:
# - 8090:8090
restart: unless-stopped
networks:
- prometheus-qbittorent-exporter
I preferred not to expose the port and connect it directly through the external docker network prometheus-qbitorrent-exporter
If the password is giving any kind of error remove the '
or the "
, for example: QBITTORRENT_PASSWORD=yourpassword
Then you need to scrape those metrics:
scrape_configs:
- job_name: "qbittorrent"
static_configs:
- targets: ["<your_ip_address>:8090"]
Alertmanager alerts⚑
You'll need to tweak the next values for your case. Specially the private trackers ones, as each has their policy.
groups:
- name: qbitorrent
rules:
- alert: QbittorrentNotConnected
expr: |
absent(qbittorrent_transfer_connection_status{connection_status="connected"})
or
qbittorrent_transfer_connection_status{connection_status="connected"} != 1
for: 5m
labels:
severity: critical
annotations:
summary: "qBittorrent connection problem"
description: |
qBittorrent is either not reporting metrics (possibly down) or is not connected for more than 5 minutes.
- alert: QbittorrentFirewalled
expr: |
qbittorrent_transfer_connection_status{connection_status="firewalled"} == 1
for: 5m
labels:
severity: warning
annotations:
summary: "qBittorrent is firewalled"
- alert: QbittorrentDisconnected
expr: |
qbittorrent_transfer_connection_status{connection_status="disconnected"} == 1
for: 5m
labels:
severity: warning
annotations:
summary: "qBittorrent is disconnected"
- alert: TooManyTorrents
expr: qbittorrent_global_torrents > 900
for: 0m
labels:
severity: warning
annotations:
summary: "You should remove some to keep things balanced"
- alert: TrackerStatusDown
expr: |
# I've removed the trackers that are flaky
qbittorrent_tracker_status{
url!~".*(tracker.openbittorrent.com|opentracker.i2p.rocks|exodus.desync.com|open.tracker.cl|opentracker.i2p.rocks|tracker-udp.gbitt.info|tracker.auctor.tv|tracker.moeking.me|tracker.openbittorrent.com|tracker1.bt.moack.co.kr).*"
} > 2
for: 1d
labels:
severity: warning
service: qbittorrent
alert_type: tracker_down
annotations:
summary: "Tracker has been down for more than 1 day: {{ $labels.url }}"
description: |
Tracker "{{ $labels.url }}" has been reporting a non-working status ({{ $value }}) for more than 1 day.
This indicates the tracker may be experiencing issues or downtime.
Tracker URL: {{ $labels.url }}
Status Code: {{ $value }}
Torrent: {{ $labels.name }}
**Status Code Reference:**
- 0: ?
- 1: ?
- 2: Working
- 3: ?
- 4: Not working: time out, Tracker is currently undergoing maintenance, try again later
- alert: TorrentsInErrorState
expr: qbittorrent_torrent_states{name="error"} > 0
for: 5m
labels:
severity: warning
annotations:
summary: "There are {{ $value }} torrents in state error"
- alert: TorrentsInUnknownState
expr: qbittorrent_torrent_states{name="unknown"} > 0
for: 5m
labels:
severity: warning
annotations:
summary: "There are {{ $value }} torrents in state unknown"
- alert: TorrentsDownloadStaled
expr: qbittorrent_torrent_info{state="stalledDL"}
for: 14d
labels:
severity: info
annotations:
summary: "The torrent {{ $labels.name }} download has been stale for more than 14 days"
- alert: TorrentWithMissingFiles
expr: qbittorrent_torrent_states{name="missingFiles"} > 0
for: 5m
labels:
severity: warning
annotations:
summary: "There are {{ $value }} torrents with missing files"
- alert: TooFewUploadingTorrents
expr: qbittorrent_torrent_states{name="uploading"} < 10
for: 30m
labels:
severity: warning
annotations:
summary: "There are only {{ $value }} torrents in uploading state"
- alert: TooFewUploadTraffic
expr: avg_over_time(qbittorrent_global_upload_speed_bytes[1h]) / 1024 / 1024 < 1
for: 5h
labels:
severity: warning
annotations:
summary: "There upload speed has been lower than 1 MB/s for more than 5 hours, something may be wrong"
- name: YourPrivateTracker
rules:
- alert: YourPrivateTrackerHitAndRun
expr: |
(
# Track torrents that were present and completed
qbittorrent_torrent_info{
tracker=~".*(url1.of.your.tracker|url2.of.your.tracker).*"
} offset 5m
unless
# But are now missing
qbittorrent_torrent_info{
tracker=~".*(url1.of.your.tracker|url2.of.your.tracker).*"
}
)
and on(name)
(
# And the total active time is less than 2 weeks and 4 hours (1224000 seconds)
qbittorrent_torrent_time_active{} offset 5m < 1224000
)
for: 5m
labels:
severity: warning
service: qbittorrent
alert_type: hit_and_run
annotations:
summary: "Torrent hit-and-run detected: {{ $labels.name }}"
description: |
Torrent "{{ $labels.name }}" has been removed from qBittorrent after only {{ $value | humanizeDuration }} of seeding time since completion.
This torrent was seeded for less than the required 2 weeks (336 hours) on a private tracker.
Tracker: {{ $labels.tracker }}
Category: {{ $labels.category }}
Size: {{ $labels.size | humanize1024 }}B
Ratio achieved: {{ $labels.ratio }}
- alert: YourPrivateTrackerDownloadBufferWarning
expr: |
(
# Total uploaded bytes for the tracker
sum(
qbittorrent_torrent_total_uploaded_bytes{}
* on(name) group_left(tracker)
qbittorrent_torrent_info{
tracker=~".*(url1.of.your.tracker|url2.of.your.tracker).*"
}
)
-
# Total downloaded bytes for the tracker
sum(
qbittorrent_torrent_total_downloaded_bytes{}
* on(name) group_left(tracker)
qbittorrent_torrent_info{
tracker=~".*(url1.of.your.tracker|url2.of.your.tracker).*"
}
)
# Converted to terabytes
) / (1024 * 1024 * 1024 * 1024)
# It's the buffer I have (1TB of more downloaded data than uploaded)
< -1
for: 5m
labels:
severity: warning
service: qbittorrent
alert_type: ratio
annotations:
summary: "Tracker buffer is at risk"
description: |
This indicates more data has been downloaded than uploaded across all torrents for this tracker.
Consider prioritizing seeding for torrents from this tracker to improve the overall ratio.
Tracker: YourPrivateTracker
Amount buffer lost: {{ $value }}TB
Grafana dashboard⚑
There is this dashboard that works with the previous exporter
Logs monitorisation⚑
If you're using binhex docker the qbitorrent logs are not being exposed in the stdout of the docker but in the internal docker path /data/qBittorrent/data/logs/qbittorrent.log
. So you'll need to scrape it with promtail:
- job_name: torrent
static_configs:
- targets:
- localhost
labels:
job: qbittorrent
service_name: torrent
__path__: /your/host/data/volume/qBittorrent/data/logs/qbittorrent.log
Then you can create alerts on those logs (some of them are on the stdout of the docker and others on that file content):
groups:
- name: torrent
rules:
- alert: TorrentVPNCertificateExpired
expr: |
count_over_time({container=~"torrent"} |= `certificate has expired`[5m]) > 0
for: 1m
labels:
severity: critical
annotations:
summary: "The VPN certificate has expired. Please renew. Runbook is in the docker-compose README.md"
- alert: TorrentVPNConfigError
expr: |
count_over_time({container=~"torrent"} |~ `Error opening configuration file.*openvpn`[5m]) > 0
for: 1m
labels:
severity: critical
annotations:
summary: "There was an error opening the openvpn config file."
- alert: TorrentDockerGenericError
expr: |
count_over_time({container=~"torrent"} |~ `(?i)error` != `could not load module /lib/modules/iptable_mangle.ko`[5m]) > 0
for: 5m
labels:
severity: critical
annotations:
summary: "There are unhandled errors in the torrent logs."
- alert: TorrentQBitorrentGenericError
expr: |
count_over_time({service_name=~"torrent"} |= `(W)` != `WebAPI login failure` != `Removed torrent but failed to delete its content` [5m]) > 0
for: 5m
labels:
severity: critical
annotations:
summary: "There are unhandled errors in the qbittorrent application logs."
- alert: TorrentWebAPILoginError
expr: |
count_over_time({service_name=~"torrent"} |= `WebAPI login failure. Reason: invalid credentials`[5m]) > 3
for: 1m
labels:
severity: warning
annotations:
summary: "There were more than 3 login attempts in the torrent web ui"
- alert: TorrentWebAPILoginBan
expr: |
count_over_time({service_name=~"torrent"} |= `WebAPI login failure. Reason: IP has been banned`[5m]) > 0
for: 1m
labels:
severity: warning
annotations:
summary: "There has been an IP banned due to too many login attempts in the torrent web ui"
- alert: TorrentDeleteContentError
expr: |
count_over_time({service_name=~"torrent"} |= `Removed torrent but failed to delete its content` [5m]) > 0
for: 1m
labels:
severity: warning
annotations:
summary: "There has been an IP banned due to too many login attempts in the torrent web ui"
Automatic operation⚑
I've created a Qbittorrent operator to do the following operations: be done by a program. For example:
- Remove the torrents of a category if their ratio is above X (not all torrents).
- Remove torrents if you have more than a defined number of torrents in an intelligent way (not removing the private torrents that still need to be seeded, or torrents that are in a protected category, and order the removal by the seeding time).
- Remove unregistered torrents
- For the trackers where you're building some ratio keep the interesting torrents for a while until you build the desired buffer.
- Remove the directories that are not being used by any active torrent.
- Set the category of special trackers once imported
- Move the torrents from the blackhole the completed directory
An with the logs you can create the next alerts:
- name: qbitops
rules:
- alert: SpecialTrackerTorrentDeleted
expr: |
count_over_time({container="qbitops"} |~ `Deleting.*bibliotik`[5m]) > 0
for: 1m
labels:
severity: warning
annotations:
summary: "A torrent of the Special Tracker was automatically removed by qbitops, you may want to take a look"
- alert: UnregisteredNonCompletedTorrent
expr: |
count_over_time({container="qbitops"} |= `Unregistered non completed torrent`[5m]) > 0
for: 1m
labels:
severity: warning
annotations:
summary: "A torrent that is not in a complete state has been marked as unregistered by the tracker. You may want to take a look"
- alert: UnregisteredNonProcessedTorrent
expr: |
count_over_time({container="qbitops"} |= `Unregistered non processed torrent`[5m]) > 0
for: 1m
labels:
severity: warning
annotations:
summary: "A completed torrent that has not yet been processed has been marked as unregistered by the tracker. You may want to import the content"
- alert: TheBufferOfTorrentsToDeleteIsLow
expr: |
count_over_time({container="qbitops"} |= `The buffer of torrents to delete is low`[5m]) > 0
for: 1m
labels:
severity: warning
annotations:
summary: "qbitops is running out of torrents to delete to keep the maximum number of torrents controlled. You may want to take a look"
- alert: UnexpectedWarningInQbitops
expr: |
count_over_time({container="qbitops"} |= `WARNING` != `The buffer of torrents to delete is low` or `Unregistered`[5m]) > 0
for: 1m
labels:
severity: warning
annotations:
summary: "An unknown warning has been found in the qbitops logs"
- alert: UnexpectedErrorInQbitops
expr: |
count_over_time({container="qbitops"} |= `ERROR` != `Could not configure the QBittorrent client` or `Could not connect to the QBittorrent server`[5m]) > 0
for: 1m
labels:
severity: critical
annotations:
summary: "An unknown error has been found in the qbitops logs"
- alert: QbitopsWrongConfigurationError
expr: |
count_over_time({container="qbitops"} |= `Could not configure the QBittorrent client`[5m]) > 0
for: 1m
labels:
severity: critical
annotations:
summary: "The QbitOps configuration may be wrong."
- alert: QbitopsConnectionError
expr: |
count_over_time({container="qbitops"} |= `Could not connect to the QBittorrent server`[5m]) > 0
for: 1m
labels:
severity: critical
annotations:
summary: "The QbitOps can't connect to the Qbittorrent server."
- alert: QbitopsUnexpectedTraceback
expr: |
count_over_time({container="qbitops"} |= `Traceback`[5m]) > 0
for: 1m
labels:
severity: critical
annotations:
summary: "There is an unhandled Python Traceback in the QbitOps logs."
- alert: QbitopsNotSuccessfulRunInAWhile
expr: |
(count_over_time({container="torrent-operator"} |= `Sleeping` [10h]) or on() vector(0)) == 0
for: 1m
labels:
severity: warning
annotations:
summary: "Qbitops has not run in the expected time"
I'm currently a little bit lazy to clean it up to publish it. If you're interested you can contact me.
Client recovery⚑
When your download client stops working and you can't recover it soon your heart gets a hiccup. You'll probably start browsing the private trackers webpages to see if you have a Hit and Run and if you can solve it before you get banned. If this happens while you're away from your infrastructure it can be even worse.
Something you can do in these cases is to have another client configured so you can spawn it fast and import the torrents that are under the Hit and Run threat.
Tools⚑
- qbittools: a feature rich CLI for the management of torrents in qBittorrent.
- qbit_manage: tool will help manage tedious tasks in qBittorrent and automate them.
Troubleshooting⚑
Torrents have very low speeds when they used to work just fine⚑
If you're using a vpn, change the vpn server
Trackers stuck on Updating⚑
Sometimes the issue comes from an improvable configuration. In advanced:
- Ensure that there are enough Max concurrent http announces: I changed from 50 to 500
- Select the correct interface and Optional IP address to bind to. In my case I selected
tun0
as I'm using a vpn andAll IPv4 addresses
as I don't use IPv6.
Not there yet⚑
Protect the webui behind authentik⚑
There is still no way to avoid to enter the qbittorrent password