trex Bot 925f9619be
test / unit-tests (push) Failing after 4s
test / komodo-deploy-script (push) Successful in 16s
test / unit-tests (pull_request) Failing after 3s
test / komodo-deploy-script (pull_request) Successful in 16s
ci / docker (push) Successful in 1m37s
test: audit suite for meaningfulness, reach 100% coverage
The suite had 8 empty `pass` tests, two tests that made live network
calls to a production healthcheck endpoint, and a write_csv test that
only asserted the file existed. CI ran only test_komodo_deploy, so the
main suite never executed at all.

- Split tests by module: test_qlist, test_qprocess, test_qlogging_extra,
  test_apprise, test_qbt; rewrote test_qbitmaid and test_write_csv.
- qbt_fakes.py: shared fakes (FakeQbt, FakeQbtClient, FakeAPIError,
  StubPushover, FailingPushover, torrent factory).
- test_qbt.py loads the hyphenated qbit-maid.py via importlib and covers
  the __main__ entry point.
- Replaced the live-network tests with a RecordingRequests stub
  (suite time 14s -> 0.035s).
- .coveragerc + CI unit-tests job running the full suite with
  --fail-under=100.

127 unit tests, 100% statement coverage on all 5 first-party modules
(440 stmts), 15/15 mutations caught.

Seven pre-existing bugs are pinned by tests with KNOWN BUG docstrings
and left unfixed pending a decision.
2026-11-03 15:34:57 -06:00
2026-11-03 15:10:03 -06:00
2023-06-27 12:43:10 -05:00
2023-06-04 13:05:26 -05:00
2023-06-04 13:05:26 -05:00
2026-04-08 19:10:04 -05:00
2023-06-04 13:05:26 -05:00
2023-06-04 13:05:26 -05:00
2026-11-03 15:10:03 -06:00
2023-06-04 13:05:26 -05:00

qbit-maid

Warning: This application removes torrents that are over the minimum age and that are not part of the ignored categories, domains or tags. Please use the delete_torrents feature set to false when first testing its functionality.

Build Status

The objective is to remove torrents based on the following criteria:

  • tracker domain name
  • age
  • ratio
  • state

Install

Docker(Recommended)

package

docker pull git.jonb.io/jblu/qbit-maid:latest

Docker Run Command:

Please note it is best practice to escape spaces in variables. That is why there is backslashes in the cron schedule.

docker run --name qbit-maid -v /opt/qbit-maid:/config/ -e CRON=0\ 1\ *\ *\ * -e toml_path=/config/config.toml git.jonb.io/jblu/qbit-maid

Docker Compose

version: '3.3'
services:
    qbit-maid:
        image: git.jonb.io/jblu/qbit-maid
        container_name: qbit-maid
        volumes:
            - /opt/qbit-maid:/config
        environment:
            - CRON="0 1 * * *"
            - toml_path=/config/config.toml

Via Git

git clone https://git.jonb.io/jblu/qbit-maid.git

Qbit-maid will look for an environment variable toml_path for its configuration.If it doesn't find it, it will look for a config.toml file in it's own directory.

config.toml
[qbittorrent]
host = "192.168.x.x"
port = 8080
username = "user"
password = "pass"

[logging]
use_log = true
log_level = "INFO"
log_path = "./qc.log"
# Optional: log rotation (defaults shown)
log_max_bytes = 5000000
log_backup_count = 3

[app_tags]
protected_tag = "ipt"
non_protected_tag = "public"

[torrent]
age = 2419200
minimum_age = 432000
delete_torrents = false

[pushover]
use_pushover = false
po_key = "<key>"
po_token = "<token>"
# Notification formatting
# format: "plain" (aligned text) or "html" (bold labels, Pushover HTML)
format = "plain"
use_emoji = false
include_hostname = false
notify_on_empty = true
# Optional: -2..2. Priority 2 also needs retry/expire (defaults shown).
# priority = 0
# sound = "pushover"
# retry = 60
# expire = 3600

[apprise]
use_apprise = false
host = "192.168.x.x"
port = 8088
aurls = 'mailto://user:pass@gmail.com'

[dragnet]
enable_dragnet = false
dragnet_outfile = "./orphaned.csv"

[telemetry]
enable_telemetry = false
telemetry_outfile = "./telemetry.csv"

[ignored_categories]
tech = "tech"
books = "books"
general = "general"

[ignored_domains]
iptorrents-empirehost = "ssl.empirehost.me"
iptorrents-stackoverflow = "localhost.stackoverflow.tech"
iptorrents-bgp = "routing.bgp.technology"

[ignored_tags]
save = "save"

[healthcheck]
use_healthcheck = false
healthcheck_url = "https://example.com/ping/<uuid>>"

Logging

qbit-maid uses a named logger (qbit-maid) that writes to both the configured log file and the console (useful for Docker/cron output).

  • use_log — enable or disable logging entirely.
  • log_level — one of DEBUG, INFO, WARN, ERROR, CRITICAL.
  • log_path — path to the log file.
  • log_max_bytes — rotate the log file once it reaches this size (default 5 MB).
  • log_backup_count — number of rotated backups to keep (default 3).

Log lines include the timestamp, level, logger name, function, and line number, for example:

10/26/2026 10:10:00 PM [INFO] qbit-maid:tor_processor:24 - Submitted ["My Torrent..."] for deletion.

Notifications

Pushover and Apprise share one summary builder, so both report the same numbers. The title states the outcome, the body gives the scanned total, the actionable counters, then the tracker split.

The default summary looks like this:

qbit-maid: 7 torrents removed

Scanned 42 torrents in 12s

🗑️ 7 removed  ·  👻 4 orphaned
⏭️ 5 ignored  ·  🐣 3 premature

🛡️ 10 protected  ·  🌐 20 public

Each counter is emoji value label, grouped two per line and joined with a middle dot. The value leads so the numbers are the first thing the eye lands on, and the separator survives any font — Pushover renders the body in a proportional font, so space-padded columns collapse into a ragged list on a phone.

format = "html" bolds the values using Pushover's HTML subset (html=1 is set automatically). Apprise has no HTML support, so it always receives the plain body.

use_emoji defaults to true. Set it to false for a plain-text variant:

Scanned 42 torrents in 12s

7 removed  ·  4 orphaned
5 ignored  ·  3 premature

10 protected  ·  20 public

To preview the result without sending a push, render the real build_summary() output as a phone-width mockup:

python3 docs/notify-preview.py /tmp/notify.png

Pass --mock to render the design sketches instead of the live output.

Other options:

  • include_hostname — add a Host: line, useful when several instances notify the same device.
  • notify_on_empty — set to false to skip the push when nothing was removed or orphaned.
  • priority / sound — passed through to Pushover. Priority 2 automatically adds retry and expire.

When delete_torrents = false the summary says Would remove instead of Removed, so a dry run is never mistaken for a real one.

S
Description
Qbittorrent cleaner that deletes torrents based on age, categories, domains and tags.
Readme
607 KiB
0 Stars 1 Watchers 0 Forks
qbit-maid
Latest
2026-11-03 23:57:51 -06:00
Languages
Python 95.4%
Shell 4.5%
Dockerfile 0.1%