A powerful client-server automation engine for qBittorrent with HTTP API, persistent job queue, and Docker-first deployment.
qbt-rules is a client-server automation engine for qBittorrent that processes torrent management rules through a persistent job queue. Define YAML-based rules to automatically categorize, tag, pause, resume, delete, and manage torrents based on flexible conditions.
v0.4.0 introduces a complete architectural transformation:
- π Client-Server Architecture - HTTP API with background worker
- π¦ Persistent Job Queue - SQLite (default) or Redis backends
- π³ Docker-First Deployment - Containerized with multi-platform support (amd64, arm64)
- π API Key Authentication - Secure webhook endpoints
- π Job Management - Track execution history, status, and statistics
- β‘ Sequential Processing - Prevent race conditions with queue-based execution
Key Features:
- Declarative YAML Rules - No coding required
- Reusable References (v0.5.0+) - Define variables, conditions, and actions once; use everywhere
- Multiple Execution Contexts - Manual, weekly-cleanup (cron), webhooks (torrent-imported, download-finished)
- Powerful Conditions - Complex logic with AND/OR/NOT groups, 17+ operators
- Rich Field Access - Dot notation access to all torrent metadata (info., trackers., files.*, etc.)
- Idempotent Actions - Safe to run repeatedly
- RESTful HTTP API - Job submission, status tracking, statistics
- Universal Docker Secrets - All config values support
_FILEsuffix - Multi-Version qBittorrent Support - v4.1+ through v5.1.4+ via qbittorrent-api
- Docker and Docker Compose
- qBittorrent with Web UI enabled
# Create directory structure
mkdir -p qbt-rules/config
cd qbt-rulesNote: On first run, default configuration files (
config.ymlandrules.yml) will be automatically created in the/configdirectory. You can then edit them to customize your setup.
version: '3.8'
services:
qbt-rules:
image: ghcr.io/andronics/qbt-rules:latest
container_name: qbt-rules
restart: unless-stopped
ports:
- "5000:5000"
volumes:
- ./config:/config
environment:
# Server configuration
QBT_RULES_SERVER_API_KEY: "your-secure-api-key-here" # Change this!
# qBittorrent connection
QBT_RULES_QBITTORRENT_HOST: "http://qbittorrent:8080"
QBT_RULES_QBITTORRENT_USERNAME: "admin"
QBT_RULES_QBITTORRENT_PASSWORD: "adminpass"
# Queue (SQLite default)
QBT_RULES_QUEUE_BACKEND: "sqlite"
QBT_RULES_QUEUE_SQLITE_PATH: "/config/qbt-rules.db"# Start container
docker-compose up -d
# Check health
curl http://localhost:5000/api/health
# View logs
docker-compose logs -f qbt-rulesEdit config/rules.yml:
rules:
- name: "Auto-categorize HD movies"
enabled: true
stop_on_match: true
context: torrent-imported # Runs when torrents are added
conditions:
all:
- field: info.name
operator: matches
value: '(?i).*(1080p|2160p|4k).*'
actions:
- type: set_category
params:
category: "Movies-HD"
- type: add_tag
params:
tags:
- hd
- auto-categorizedUsing HTTP API:
# Queue a weekly-cleanup rules job
curl -X POST "http://localhost:5000/api/execute?context=weekly-cleanup&key=your-api-key"
# Process specific torrent (webhook)
curl -X POST "http://localhost:5000/api/execute?context=download-finished&hash=abc123...&key=your-api-key"
# Check job status
curl "http://localhost:5000/api/jobs?key=your-api-key" | jq
# Get statistics
curl "http://localhost:5000/api/stats?key=your-api-key" | jqUsing CLI (client mode):
# Install CLI client (optional)
docker exec qbt-rules qbt-rules --context weekly-cleanup --wait
# Or from host (if qbt-rules pip package installed)
pip install qbt-rules
qbt-rules --context weekly-cleanup --client-server-url http://localhost:5000 --client-api-key your-api-keyqbt-rules v0.4.0 uses a client-server architecture with persistent job queue:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β CLIENT LAYER β
β β
β Webhook β HTTP API β Cron Container β Manual CLI β
β β β
β βββββ POST /api/execute?context=X&key=Y β
ββββββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β SERVER LAYER β
β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Flask HTTP API (Gunicorn) β β
β β β’ POST /api/execute - Queue job β β
β β β’ GET /api/jobs - List jobs β β
β β β’ GET /api/health - Health check β β
β β β’ GET /api/stats - Statistics β β
β βββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββ β
β β β
β βΌ β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Queue Manager (SQLite or Redis) β β
β β β’ Persistent job storage β β
β β β’ Job states: pending β processing β completed β β
β βββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββ β
β β β
β βΌ β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Worker Thread (Background) β β
β β β’ Dequeues jobs sequentially β β
β β β’ Executes via RulesEngine β β
β β β’ Updates job status with results β β
β βββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββ β
β β β
β βΌ β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Rules Engine β β
β β β’ Loads rules.yml β β
β β β’ Evaluates conditions (context filter) β β
β β β’ Executes actions (idempotent) β β
β βββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββ β
ββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββ
β qBittorrent β
β Server β
βββββββββββββββ
Benefits:
- Sequential Execution: Jobs process one at a time (no race conditions)
- Persistence: Jobs survive server restarts
- Monitoring: Track job history, status, and execution stats
- Scalability: Optional Redis backend for high webhook volume
π Complete Architecture Documentation
Contexts filter which rules execute. Rules specify a context: condition, and jobs are submitted with a context parameter.
# Submit job with context
curl -X POST "http://localhost:5000/api/execute?context=weekly-cleanup&key=YOUR_KEY"Common Contexts:
| Context | Use Case | Trigger Method |
|---|---|---|
weekly-cleanup |
Periodic maintenance | Cron container or systemd timer |
torrent-imported |
Torrent added event | qBittorrent webhook |
download-finished |
Download completed | qBittorrent webhook |
adhoc-run |
Manual execution | CLI or API call |
Example Rule with Context:
rules:
- name: "Cleanup old torrents"
context: weekly-cleanup # Only runs when context=weekly-cleanup
conditions:
all:
- field: info.completion_on
operator: older_than
value: "30 days"
actions:
- type: delete_torrent
params:
keep_files: trueCombine conditions with logical groups:
- all - AND logic (all conditions must match)
- any - OR logic (at least one must match)
- none - NOT logic (no conditions can match)
Available Operators:
==, !=, >, <, >=, <=, contains, not_contains, matches, in, not_in, older_than, newer_than, smaller_than, larger_than
Example:
- name: "High ratio seeded torrents"
context: weekly-cleanup
conditions:
all:
- field: info.ratio
operator: '>='
value: 2.0
- field: info.seeding_time
operator: '>'
value: 604800 # 7 days in seconds
none:
- field: info.category
operator: in
value: [seedbox, long-term]
actions:
- type: add_tag
params:
tags:
- ready-to-removeAccess torrent data using dot notation across 8 API categories:
| Prefix | Description | Example Fields |
|---|---|---|
info.* |
Main torrent info | info.name, info.size, info.ratio, info.state |
trackers.* |
Tracker data (collection) | trackers.url, trackers.status, trackers.msg |
files.* |
File list (collection) | files.name, files.size, files.progress |
properties.* |
Extended properties | properties.save_path, properties.comment |
transfer.* |
Global transfer stats | transfer.dl_speed, transfer.up_speed |
app.* |
Application info | app.version, app.encryption |
peers.* |
Peer data (collection) | peers.ip, peers.client, peers.progress |
webseeds.* |
Web seed data (collection) | webseeds.url |
Execute one or more actions when conditions match:
| Action | Description |
|---|---|
stop / start / force_start |
Control torrent state |
recheck / reannounce |
Maintenance operations |
delete_torrent |
Remove torrent (with/without files) |
set_category |
Set category |
add_tag / remove_tag / set_tags |
Tag management |
set_upload_limit / set_download_limit |
Speed limiting |
Example:
actions:
- type: set_category
params:
category: "Movies-HD"
- type: add_tag
params:
tags:
- hd
- private
- type: set_upload_limit
params:
limit: 1048576 # 1 MB/s in bytesConfigure qBittorrent to fire webhooks on events:
- qBittorrent Settings β Web UI β Advanced
- Run external program on torrent completion:
curl -X POST "http://qbt-rules:5000/api/execute?context=download-finished&hash=%I&key=YOUR_API_KEY"
qBittorrent Variables:
%I- Torrent hash%N- Torrent name%L- Category%F- Content path
Add to docker-compose.yml:
services:
qbt-rules-cron:
image: alpine:latest
restart: unless-stopped
command: >
sh -c "
apk add --no-cache curl &&
echo '*/5 * * * * curl -X POST http://qbt-rules:5000/api/execute?context=weekly-cleanup&key=YOUR_KEY' | crontab - &&
crond -f -l 2
"
depends_on:
- qbt-rules
networks:
- qbt-networkFor high webhook volume, use Redis:
version: '3.8'
services:
qbt-rules:
image: ghcr.io/andronics/qbt-rules:latest
environment:
QBT_RULES_QUEUE_BACKEND: "redis"
QBT_RULES_QUEUE_REDIS_URL: "redis://redis:6379/0"
depends_on:
redis:
condition: service_healthy
redis:
image: redis:7-alpine
command: redis-server --appendonly yes
volumes:
- redis-data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
volumes:
redis-data:Queue a rules execution job.
Request:
curl -X POST "http://localhost:5000/api/execute?context=weekly-cleanup&key=YOUR_KEY"Response (202 Accepted):
{
"job_id": "550e8400-e29b-41d4-a716-446655440000",
"context": "weekly-cleanup",
"status": "pending",
"created_at": "2024-01-15T10:30:00.123456"
}List jobs with filtering and pagination.
Request:
curl "http://localhost:5000/api/jobs?status=completed&limit=10&key=YOUR_KEY"Response:
{
"total": 150,
"jobs": [
{
"job_id": "...",
"context": "weekly-cleanup",
"status": "completed",
"result": {
"total_torrents": 42,
"rules_matched": 5,
"actions_executed": 8
}
}
]
}Health check endpoint (no authentication required).
Request:
curl http://localhost:5000/api/healthResponse:
{
"status": "healthy",
"version": "0.4.0",
"queue": {
"backend": "SQLiteQueue",
"pending_jobs": 2
},
"worker": {
"status": "running"
}
}All variables support _FILE suffix for Docker secrets:
environment:
# Server
QBT_RULES_SERVER_HOST: "0.0.0.0"
QBT_RULES_SERVER_PORT: "5000"
QBT_RULES_SERVER_API_KEY: "your-secret-key"
# Or use Docker secret:
# QBT_RULES_SERVER_API_KEY_FILE: "/run/secrets/api_key"
# Queue
QBT_RULES_QUEUE_BACKEND: "sqlite" # or "redis"
QBT_RULES_QUEUE_SQLITE_PATH: "/config/qbt-rules.db"
QBT_RULES_QUEUE_CLEANUP_AFTER: "7d"
# qBittorrent
QBT_RULES_QBITTORRENT_HOST: "http://qbittorrent:8080"
QBT_RULES_QBITTORRENT_USERNAME: "admin"
QBT_RULES_QBITTORRENT_PASSWORD: "adminpass"
# Or use Docker secret:
# QBT_RULES_QBITTORRENT_PASSWORD_FILE: "/run/secrets/qbt_password"
# Logging
LOG_LEVEL: "INFO" # DEBUG, INFO, WARNING, ERROR
TRACE_MODE: "false"services:
qbt-rules:
environment:
QBT_RULES_SERVER_API_KEY_FILE: "/run/secrets/api_key"
QBT_RULES_QBITTORRENT_PASSWORD_FILE: "/run/secrets/qbt_password"
secrets:
- api_key
- qbt_password
secrets:
api_key:
file: ./secrets/api_key.txt
qbt_password:
file: ./secrets/qbt_password.txt- name: "Categorize TV shows"
enabled: true
context: torrent-imported
conditions:
all:
- field: info.name
operator: matches
value: '(?i).*[Ss]\d{2}[Ee]\d{2}.*'
actions:
- type: set_category
params:
category: "TV-Shows"- name: "Delete old seeded torrents"
enabled: true
context: weekly-cleanup
conditions:
all:
- field: info.completion_on
operator: older_than
value: "30 days"
- field: info.ratio
operator: ">="
value: 2.0
none:
- field: info.category
operator: in
value: [keep, seedbox]
actions:
- type: delete_torrent
params:
keep_files: true- name: "Pause large downloads during daytime"
enabled: true
context: weekly-cleanup
conditions:
all:
- field: info.size
operator: larger_than
value: "50 GB"
- field: info.state
operator: contains
value: "DL"
actions:
- type: stop
- type: add_tag
params:
tags:
- large-download-pausedπ More Examples
The resolver layer allows you to define reusable variables, conditions, and actions to reduce repetition and make rules more maintainable.
- DRY Principle: Define once, reference many times
- Type-Aware Variables: Preserve types (floats, lists, etc.)
- Composable Logic: Mix references with inline conditions
- Future-Ready: Designed for upcoming features (schedules, notifications, multi-instance)
refs:
vars:
min_ratio: 1.5
cleanup_age: "30 days"
protected_categories: ["keep", "archive"]
conditions:
well-seeded:
all:
- field: info.ratio
operator: ">="
value: ${vars.min_ratio}
- field: info.completion_on
operator: older_than
value: ${vars.cleanup_age}
protected:
any:
- field: info.category
operator: in
value: ${vars.protected_categories}
actions:
safe-delete:
- type: add_tag
params:
tags: ["pending-delete"]
- type: stop
rules:
- name: "Cleanup well-seeded torrents"
enabled: true
conditions:
- $ref: conditions.well-seeded
- none:
- $ref: conditions.protected
actions:
- $ref: actions.safe-deleteTwo Resolution Mechanisms:
-
Variable Substitution (
${vars.name}): Replace placeholders with values${vars.min_ratio}β1.5(preserves float type)${vars.protected_categories}β["keep", "archive"](preserves list)"Ratio: ${vars.min_ratio}"β"Ratio: 1.5"(string interpolation)
-
Reference Expansion (
$ref: path): Replace with entire structure$ref: conditions.well-seededβ Expands to full condition block$ref: actions.safe-deleteβ Expands to action sequence
Resolution Order:
- Expand all
$refreferences (recursive) - Substitute all
${vars.*}variables (type-aware) - Cache resolved rules for performance
All reusable components live under the refs key:
refs:
vars: # Scalar values, lists, simple config
key: value
conditions: # Condition groups: {all|any|none: [...]}
name:
all: [...]
actions: # Action sequences: [{type, params}, ...]
name:
- type: action_type
params: {...}All references use explicit dot notation:
- Variables:
${vars.variable_name} - Conditions:
$ref: conditions.condition_name - Actions:
$ref: actions.action_name
Rules without refs block work unchanged. The resolver is opt-in.
π Complete Resolver Documentation
- Architecture Documentation - System design and components
- API Reference - Complete HTTP API documentation
- Docker Deployment Guide - Container setup and examples
- Configuration Examples - Server/client/queue config
- Rules Examples - Comprehensive rule examples
qbt-rules v0.4.0 introduces breaking changes:
- Distribution: PyPI package β Docker images (ghcr.io)
- Architecture: Standalone CLI β Client-server with HTTP API
- Terminology:
triggerβcontext(backward compatible flags exist) - Execution: Direct execution β Queue-based job processing
- Rules File: No changes needed (rules.yml syntax unchanged)
- Config File: Update structure (see config.default.yml)
- Deployment: Replace cron/systemd with Docker Compose + webhooks/cron container
- CLI Usage: Update to use HTTP API or client mode
- Rules syntax unchanged (only
trigger:βcontext:) - CLI flag
--triggermapped to--contextautomatically - PyPI package v0.3.x remains available (deprecated)
- Docker and Docker Compose
- qBittorrent v4.1+ with Web UI enabled
- Optional: Redis server (for high-performance queue backend)
See CHANGELOG.md for version history and release notes.
Latest Release: v0.4.0 - 2024-12-14
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'feat: add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project is released into the public domain under the Unlicense.
You are free to use, modify, and distribute this software for any purpose without attribution.
Happy automating! ππ³
For complete documentation, see docs/.