{"id":36964020,"url":"https://github.com/andronics/qbt-rules","last_synced_at":"2026-01-13T19:13:42.402Z","repository":{"id":327994385,"uuid":"1113734989","full_name":"andronics/qbt-rules","owner":"andronics","description":"Rules Based Automated Torrent Management Engine","archived":false,"fork":false,"pushed_at":"2026-01-01T22:11:51.000Z","size":379,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-01-07T08:55:09.047Z","etag":null,"topics":["actions","automation","conditions","management","qbittorrent","rules","rules-engine","torrent","triggers"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"unlicense","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/andronics.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-12-10T11:46:27.000Z","updated_at":"2026-01-01T22:11:54.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/andronics/qbt-rules","commit_stats":null,"previous_names":["andronics/qbittorrent-automation"],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/andronics/qbt-rules","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andronics%2Fqbt-rules","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andronics%2Fqbt-rules/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andronics%2Fqbt-rules/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andronics%2Fqbt-rules/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/andronics","download_url":"https://codeload.github.com/andronics/qbt-rules/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andronics%2Fqbt-rules/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28397826,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-13T14:36:09.778Z","status":"ssl_error","status_checked_at":"2026-01-13T14:35:19.697Z","response_time":56,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["actions","automation","conditions","management","qbittorrent","rules","rules-engine","torrent","triggers"],"created_at":"2026-01-13T19:13:41.736Z","updated_at":"2026-01-13T19:13:42.396Z","avatar_url":"https://github.com/andronics.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# qbt-rules\n\nA powerful client-server automation engine for qBittorrent with HTTP API, persistent job queue, and Docker-first deployment.\n\n[![GitHub Release](https://img.shields.io/github/v/release/andronics/qbt-rules)](https://github.com/andronics/qbt-rules/releases)\n[![Docker Image](https://img.shields.io/badge/docker-ghcr.io-blue)](https://github.com/andronics/qbt-rules/pkgs/container/qbt-rules)\n[![License: Unlicense](https://img.shields.io/badge/license-Unlicense-blue.svg)](http://unlicense.org/)\n\n---\n\n## What is qbt-rules?\n\nqbt-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.\n\n**v0.4.0 introduces a complete architectural transformation:**\n\n- 🚀 **Client-Server Architecture** - HTTP API with background worker\n- 📦 **Persistent Job Queue** - SQLite (default) or Redis backends\n- 🐳 **Docker-First Deployment** - Containerized with multi-platform support (amd64, arm64)\n- 🔐 **API Key Authentication** - Secure webhook endpoints\n- 📊 **Job Management** - Track execution history, status, and statistics\n- ⚡ **Sequential Processing** - Prevent race conditions with queue-based execution\n\n**Key Features:**\n\n- **Declarative YAML Rules** - No coding required\n- **Reusable References (v0.5.0+)** - Define variables, conditions, and actions once; use everywhere\n- **Multiple Execution Contexts** - Manual, weekly-cleanup (cron), webhooks (torrent-imported, download-finished)\n- **Powerful Conditions** - Complex logic with AND/OR/NOT groups, 17+ operators\n- **Rich Field Access** - Dot notation access to all torrent metadata (info.*, trackers.*, files.*, etc.)\n- **Idempotent Actions** - Safe to run repeatedly\n- **RESTful HTTP API** - Job submission, status tracking, statistics\n- **Universal Docker Secrets** - All config values support `_FILE` suffix\n- **Multi-Version qBittorrent Support** - v4.1+ through v5.1.4+ via qbittorrent-api\n\n---\n\n## Quick Start\n\n### Prerequisites\n\n- Docker and Docker Compose\n- qBittorrent with Web UI enabled\n\n### Installation\n\n```bash\n# Create directory structure\nmkdir -p qbt-rules/config\ncd qbt-rules\n```\n\n\u003e **Note:** On first run, default configuration files (`config.yml` and `rules.yml`) will be automatically created in the `/config` directory. You can then edit them to customize your setup.\n\n### Create docker-compose.yml\n\n```yaml\nversion: '3.8'\n\nservices:\n  qbt-rules:\n    image: ghcr.io/andronics/qbt-rules:latest\n    container_name: qbt-rules\n    restart: unless-stopped\n    ports:\n      - \"5000:5000\"\n    volumes:\n      - ./config:/config\n    environment:\n      # Server configuration\n      QBT_RULES_SERVER_API_KEY: \"your-secure-api-key-here\"  # Change this!\n\n      # qBittorrent connection\n      QBT_RULES_QBITTORRENT_HOST: \"http://qbittorrent:8080\"\n      QBT_RULES_QBITTORRENT_USERNAME: \"admin\"\n      QBT_RULES_QBITTORRENT_PASSWORD: \"adminpass\"\n\n      # Queue (SQLite default)\n      QBT_RULES_QUEUE_BACKEND: \"sqlite\"\n      QBT_RULES_QUEUE_SQLITE_PATH: \"/config/qbt-rules.db\"\n```\n\n### Start the Server\n\n```bash\n# Start container\ndocker-compose up -d\n\n# Check health\ncurl http://localhost:5000/api/health\n\n# View logs\ndocker-compose logs -f qbt-rules\n```\n\n### Your First Rule\n\nEdit `config/rules.yml`:\n\n```yaml\nrules:\n  - name: \"Auto-categorize HD movies\"\n    enabled: true\n    stop_on_match: true\n    context: torrent-imported  # Runs when torrents are added\n    conditions:\n      all:\n        - field: info.name\n          operator: matches\n          value: '(?i).*(1080p|2160p|4k).*'\n    actions:\n      - type: set_category\n        params:\n          category: \"Movies-HD\"\n      - type: add_tag\n        params:\n          tags:\n            - hd\n            - auto-categorized\n```\n\n### Execute Rules\n\n**Using HTTP API:**\n```bash\n# Queue a weekly-cleanup rules job\ncurl -X POST \"http://localhost:5000/api/execute?context=weekly-cleanup\u0026key=your-api-key\"\n\n# Process specific torrent (webhook)\ncurl -X POST \"http://localhost:5000/api/execute?context=download-finished\u0026hash=abc123...\u0026key=your-api-key\"\n\n# Check job status\ncurl \"http://localhost:5000/api/jobs?key=your-api-key\" | jq\n\n# Get statistics\ncurl \"http://localhost:5000/api/stats?key=your-api-key\" | jq\n```\n\n**Using CLI (client mode):**\n```bash\n# Install CLI client (optional)\ndocker exec qbt-rules qbt-rules --context weekly-cleanup --wait\n\n# Or from host (if qbt-rules pip package installed)\npip install qbt-rules\nqbt-rules --context weekly-cleanup --client-server-url http://localhost:5000 --client-api-key your-api-key\n```\n\n---\n\n## Architecture\n\nqbt-rules v0.4.0 uses a client-server architecture with persistent job queue:\n\n```\n┌─────────────────────────────────────────────────────────────────┐\n│                     CLIENT LAYER                                │\n│                                                                 │\n│  Webhook → HTTP API ← Cron Container ← Manual CLI              │\n│              │                                                  │\n│              └──── POST /api/execute?context=X\u0026key=Y            │\n└──────────────────────────────┬──────────────────────────────────┘\n                               │\n                               ▼\n┌─────────────────────────────────────────────────────────────────┐\n│                     SERVER LAYER                                │\n│                                                                 │\n│  ┌─────────────────────────────────────────────────────┐       │\n│  │  Flask HTTP API (Gunicorn)                          │       │\n│  │  • POST /api/execute  - Queue job                   │       │\n│  │  • GET  /api/jobs     - List jobs                   │       │\n│  │  • GET  /api/health   - Health check                │       │\n│  │  • GET  /api/stats    - Statistics                  │       │\n│  └─────────────────┬───────────────────────────────────┘       │\n│                    │                                            │\n│                    ▼                                            │\n│  ┌─────────────────────────────────────────────────────┐       │\n│  │  Queue Manager (SQLite or Redis)                    │       │\n│  │  • Persistent job storage                           │       │\n│  │  • Job states: pending → processing → completed     │       │\n│  └─────────────────┬───────────────────────────────────┘       │\n│                    │                                            │\n│                    ▼                                            │\n│  ┌─────────────────────────────────────────────────────┐       │\n│  │  Worker Thread (Background)                         │       │\n│  │  • Dequeues jobs sequentially                       │       │\n│  │  • Executes via RulesEngine                         │       │\n│  │  • Updates job status with results                  │       │\n│  └─────────────────┬───────────────────────────────────┘       │\n│                    │                                            │\n│                    ▼                                            │\n│  ┌─────────────────────────────────────────────────────┐       │\n│  │  Rules Engine                                       │       │\n│  │  • Loads rules.yml                                  │       │\n│  │  • Evaluates conditions (context filter)            │       │\n│  │  • Executes actions (idempotent)                    │       │\n│  └─────────────────┬───────────────────────────────────┘       │\n└────────────────────┼────────────────────────────────────────────┘\n                     │\n                     ▼\n              ┌─────────────┐\n              │ qBittorrent │\n              │   Server    │\n              └─────────────┘\n```\n\n**Benefits:**\n- **Sequential Execution**: Jobs process one at a time (no race conditions)\n- **Persistence**: Jobs survive server restarts\n- **Monitoring**: Track job history, status, and execution stats\n- **Scalability**: Optional Redis backend for high webhook volume\n\n📖 **[Complete Architecture Documentation](docs/Architecture.md)**\n\n---\n\n## Core Concepts\n\n### Execution Contexts\n\nContexts filter which rules execute. Rules specify a `context:` condition, and jobs are submitted with a context parameter.\n\n```bash\n# Submit job with context\ncurl -X POST \"http://localhost:5000/api/execute?context=weekly-cleanup\u0026key=YOUR_KEY\"\n```\n\n**Common Contexts:**\n\n| Context | Use Case | Trigger Method |\n|---------|----------|----------------|\n| `weekly-cleanup` | Periodic maintenance | Cron container or systemd timer |\n| `torrent-imported` | Torrent added event | qBittorrent webhook |\n| `download-finished` | Download completed | qBittorrent webhook |\n| `adhoc-run` | Manual execution | CLI or API call |\n\n**Example Rule with Context:**\n\n```yaml\nrules:\n  - name: \"Cleanup old torrents\"\n    context: weekly-cleanup  # Only runs when context=weekly-cleanup\n    conditions:\n      all:\n        - field: info.completion_on\n          operator: older_than\n          value: \"30 days\"\n    actions:\n      - type: delete_torrent\n        params:\n          keep_files: true\n```\n\n📖 **[Context Documentation](docs/Architecture.md#data-flow)**\n\n---\n\n### Conditions\n\nCombine conditions with logical groups:\n\n- **all** - AND logic (all conditions must match)\n- **any** - OR logic (at least one must match)\n- **none** - NOT logic (no conditions can match)\n\n**Available Operators:**\n\n`==`, `!=`, `\u003e`, `\u003c`, `\u003e=`, `\u003c=`, `contains`, `not_contains`, `matches`, `in`, `not_in`, `older_than`, `newer_than`, `smaller_than`, `larger_than`\n\n**Example:**\n```yaml\n- name: \"High ratio seeded torrents\"\n  context: weekly-cleanup\n  conditions:\n    all:\n      - field: info.ratio\n        operator: '\u003e='\n        value: 2.0\n      - field: info.seeding_time\n        operator: '\u003e'\n        value: 604800  # 7 days in seconds\n    none:\n      - field: info.category\n        operator: in\n        value: [seedbox, long-term]\n  actions:\n    - type: add_tag\n      params:\n        tags:\n          - ready-to-remove\n```\n\n---\n\n### Available Fields\n\nAccess torrent data using dot notation across 8 API categories:\n\n| Prefix | Description | Example Fields |\n|--------|-------------|----------------|\n| `info.*` | Main torrent info | `info.name`, `info.size`, `info.ratio`, `info.state` |\n| `trackers.*` | Tracker data (collection) | `trackers.url`, `trackers.status`, `trackers.msg` |\n| `files.*` | File list (collection) | `files.name`, `files.size`, `files.progress` |\n| `properties.*` | Extended properties | `properties.save_path`, `properties.comment` |\n| `transfer.*` | Global transfer stats | `transfer.dl_speed`, `transfer.up_speed` |\n| `app.*` | Application info | `app.version`, `app.encryption` |\n| `peers.*` | Peer data (collection) | `peers.ip`, `peers.client`, `peers.progress` |\n| `webseeds.*` | Web seed data (collection) | `webseeds.url` |\n\n📖 **[Complete Field Reference](config/rules.default.yml)**\n\n---\n\n### Actions\n\nExecute one or more actions when conditions match:\n\n| Action | Description |\n|--------|-------------|\n| `stop` / `start` / `force_start` | Control torrent state |\n| `recheck` / `reannounce` | Maintenance operations |\n| `delete_torrent` | Remove torrent (with/without files) |\n| `set_category` | Set category |\n| `add_tag` / `remove_tag` / `set_tags` | Tag management |\n| `set_upload_limit` / `set_download_limit` | Speed limiting |\n\n**Example:**\n```yaml\nactions:\n  - type: set_category\n    params:\n      category: \"Movies-HD\"\n  - type: add_tag\n    params:\n      tags:\n        - hd\n        - private\n  - type: set_upload_limit\n    params:\n      limit: 1048576  # 1 MB/s in bytes\n```\n\n---\n\n## Deployment Scenarios\n\n### Webhook Integration (qBittorrent)\n\nConfigure qBittorrent to fire webhooks on events:\n\n1. **qBittorrent Settings** → Web UI → Advanced\n2. **Run external program on torrent completion:**\n   ```bash\n   curl -X POST \"http://qbt-rules:5000/api/execute?context=download-finished\u0026hash=%I\u0026key=YOUR_API_KEY\"\n   ```\n\n**qBittorrent Variables:**\n- `%I` - Torrent hash\n- `%N` - Torrent name\n- `%L` - Category\n- `%F` - Content path\n\n---\n\n### Scheduled Execution (Cron Container)\n\nAdd to docker-compose.yml:\n\n```yaml\nservices:\n  qbt-rules-cron:\n    image: alpine:latest\n    restart: unless-stopped\n    command: \u003e\n      sh -c \"\n        apk add --no-cache curl \u0026\u0026\n        echo '*/5 * * * * curl -X POST http://qbt-rules:5000/api/execute?context=weekly-cleanup\u0026key=YOUR_KEY' | crontab - \u0026\u0026\n        crond -f -l 2\n      \"\n    depends_on:\n      - qbt-rules\n    networks:\n      - qbt-network\n```\n\n---\n\n### Redis Queue Backend (High Performance)\n\nFor high webhook volume, use Redis:\n\n```yaml\nversion: '3.8'\n\nservices:\n  qbt-rules:\n    image: ghcr.io/andronics/qbt-rules:latest\n    environment:\n      QBT_RULES_QUEUE_BACKEND: \"redis\"\n      QBT_RULES_QUEUE_REDIS_URL: \"redis://redis:6379/0\"\n    depends_on:\n      redis:\n        condition: service_healthy\n\n  redis:\n    image: redis:7-alpine\n    command: redis-server --appendonly yes\n    volumes:\n      - redis-data:/data\n    healthcheck:\n      test: [\"CMD\", \"redis-cli\", \"ping\"]\n      interval: 10s\n\nvolumes:\n  redis-data:\n```\n\n📖 **[Docker Deployment Guide](docs/Docker.md)**\n\n---\n\n## HTTP API Reference\n\n### POST /api/execute\n\nQueue a rules execution job.\n\n**Request:**\n```bash\ncurl -X POST \"http://localhost:5000/api/execute?context=weekly-cleanup\u0026key=YOUR_KEY\"\n```\n\n**Response (202 Accepted):**\n```json\n{\n  \"job_id\": \"550e8400-e29b-41d4-a716-446655440000\",\n  \"context\": \"weekly-cleanup\",\n  \"status\": \"pending\",\n  \"created_at\": \"2024-01-15T10:30:00.123456\"\n}\n```\n\n---\n\n### GET /api/jobs\n\nList jobs with filtering and pagination.\n\n**Request:**\n```bash\ncurl \"http://localhost:5000/api/jobs?status=completed\u0026limit=10\u0026key=YOUR_KEY\"\n```\n\n**Response:**\n```json\n{\n  \"total\": 150,\n  \"jobs\": [\n    {\n      \"job_id\": \"...\",\n      \"context\": \"weekly-cleanup\",\n      \"status\": \"completed\",\n      \"result\": {\n        \"total_torrents\": 42,\n        \"rules_matched\": 5,\n        \"actions_executed\": 8\n      }\n    }\n  ]\n}\n```\n\n---\n\n### GET /api/health\n\nHealth check endpoint (no authentication required).\n\n**Request:**\n```bash\ncurl http://localhost:5000/api/health\n```\n\n**Response:**\n```json\n{\n  \"status\": \"healthy\",\n  \"version\": \"0.4.0\",\n  \"queue\": {\n    \"backend\": \"SQLiteQueue\",\n    \"pending_jobs\": 2\n  },\n  \"worker\": {\n    \"status\": \"running\"\n  }\n}\n```\n\n📖 **[Complete API Reference](docs/API.md)**\n\n---\n\n## Configuration\n\n### Environment Variables\n\nAll variables support `_FILE` suffix for Docker secrets:\n\n```yaml\nenvironment:\n  # Server\n  QBT_RULES_SERVER_HOST: \"0.0.0.0\"\n  QBT_RULES_SERVER_PORT: \"5000\"\n  QBT_RULES_SERVER_API_KEY: \"your-secret-key\"\n  # Or use Docker secret:\n  # QBT_RULES_SERVER_API_KEY_FILE: \"/run/secrets/api_key\"\n\n  # Queue\n  QBT_RULES_QUEUE_BACKEND: \"sqlite\"  # or \"redis\"\n  QBT_RULES_QUEUE_SQLITE_PATH: \"/config/qbt-rules.db\"\n  QBT_RULES_QUEUE_CLEANUP_AFTER: \"7d\"\n\n  # qBittorrent\n  QBT_RULES_QBITTORRENT_HOST: \"http://qbittorrent:8080\"\n  QBT_RULES_QBITTORRENT_USERNAME: \"admin\"\n  QBT_RULES_QBITTORRENT_PASSWORD: \"adminpass\"\n  # Or use Docker secret:\n  # QBT_RULES_QBITTORRENT_PASSWORD_FILE: \"/run/secrets/qbt_password\"\n\n  # Logging\n  LOG_LEVEL: \"INFO\"  # DEBUG, INFO, WARNING, ERROR\n  TRACE_MODE: \"false\"\n```\n\n### Docker Secrets (Recommended)\n\n```yaml\nservices:\n  qbt-rules:\n    environment:\n      QBT_RULES_SERVER_API_KEY_FILE: \"/run/secrets/api_key\"\n      QBT_RULES_QBITTORRENT_PASSWORD_FILE: \"/run/secrets/qbt_password\"\n    secrets:\n      - api_key\n      - qbt_password\n\nsecrets:\n  api_key:\n    file: ./secrets/api_key.txt\n  qbt_password:\n    file: ./secrets/qbt_password.txt\n```\n\n📖 **[Configuration Reference](config/config.default.yml)**\n\n---\n\n## Example Rules\n\n### Auto-Categorize TV Shows\n\n```yaml\n- name: \"Categorize TV shows\"\n  enabled: true\n  context: torrent-imported\n  conditions:\n    all:\n      - field: info.name\n        operator: matches\n        value: '(?i).*[Ss]\\d{2}[Ee]\\d{2}.*'\n  actions:\n    - type: set_category\n      params:\n        category: \"TV-Shows\"\n```\n\n### Delete Old Completed Torrents\n\n```yaml\n- name: \"Delete old seeded torrents\"\n  enabled: true\n  context: weekly-cleanup\n  conditions:\n    all:\n      - field: info.completion_on\n        operator: older_than\n        value: \"30 days\"\n      - field: info.ratio\n        operator: \"\u003e=\"\n        value: 2.0\n    none:\n      - field: info.category\n        operator: in\n        value: [keep, seedbox]\n  actions:\n    - type: delete_torrent\n      params:\n        keep_files: true\n```\n\n### Pause Large Downloads\n\n```yaml\n- name: \"Pause large downloads during daytime\"\n  enabled: true\n  context: weekly-cleanup\n  conditions:\n    all:\n      - field: info.size\n        operator: larger_than\n        value: \"50 GB\"\n      - field: info.state\n        operator: contains\n        value: \"DL\"\n  actions:\n    - type: stop\n    - type: add_tag\n      params:\n        tags:\n          - large-download-paused\n```\n\n📖 **[More Examples](config/rules.default.yml)**\n\n---\n\n## Reusable References (v0.5.0+)\n\nThe **resolver layer** allows you to define reusable variables, conditions, and actions to reduce repetition and make rules more maintainable.\n\n### Benefits\n\n- **DRY Principle**: Define once, reference many times\n- **Type-Aware Variables**: Preserve types (floats, lists, etc.)\n- **Composable Logic**: Mix references with inline conditions\n- **Future-Ready**: Designed for upcoming features (schedules, notifications, multi-instance)\n\n### Quick Example\n\n```yaml\nrefs:\n  vars:\n    min_ratio: 1.5\n    cleanup_age: \"30 days\"\n    protected_categories: [\"keep\", \"archive\"]\n\n  conditions:\n    well-seeded:\n      all:\n        - field: info.ratio\n          operator: \"\u003e=\"\n          value: ${vars.min_ratio}\n        - field: info.completion_on\n          operator: older_than\n          value: ${vars.cleanup_age}\n\n    protected:\n      any:\n        - field: info.category\n          operator: in\n          value: ${vars.protected_categories}\n\n  actions:\n    safe-delete:\n      - type: add_tag\n        params:\n          tags: [\"pending-delete\"]\n      - type: stop\n\nrules:\n  - name: \"Cleanup well-seeded torrents\"\n    enabled: true\n    conditions:\n      - $ref: conditions.well-seeded\n      - none:\n          - $ref: conditions.protected\n    actions:\n      - $ref: actions.safe-delete\n```\n\n### How It Works\n\n**Two Resolution Mechanisms:**\n\n1. **Variable Substitution** (`${vars.name}`): Replace placeholders with values\n   - `${vars.min_ratio}` → `1.5` (preserves float type)\n   - `${vars.protected_categories}` → `[\"keep\", \"archive\"]` (preserves list)\n   - `\"Ratio: ${vars.min_ratio}\"` → `\"Ratio: 1.5\"` (string interpolation)\n\n2. **Reference Expansion** (`$ref: path`): Replace with entire structure\n   - `$ref: conditions.well-seeded` → Expands to full condition block\n   - `$ref: actions.safe-delete` → Expands to action sequence\n\n**Resolution Order:**\n1. Expand all `$ref` references (recursive)\n2. Substitute all `${vars.*}` variables (type-aware)\n3. Cache resolved rules for performance\n\n### Structure\n\nAll reusable components live under the `refs` key:\n\n```yaml\nrefs:\n  vars:           # Scalar values, lists, simple config\n    key: value\n\n  conditions:     # Condition groups: {all|any|none: [...]}\n    name:\n      all: [...]\n\n  actions:        # Action sequences: [{type, params}, ...]\n    name:\n      - type: action_type\n        params: {...}\n```\n\n### Path Notation\n\nAll references use explicit dot notation:\n\n- Variables: `${vars.variable_name}`\n- Conditions: `$ref: conditions.condition_name`\n- Actions: `$ref: actions.action_name`\n\n### Backward Compatibility\n\nRules without `refs` block work unchanged. The resolver is opt-in.\n\n📖 **[Complete Resolver Documentation](docs/resolver-layer.md)**\n\n---\n\n## Documentation\n\n- **[Architecture Documentation](docs/Architecture.md)** - System design and components\n- **[API Reference](docs/API.md)** - Complete HTTP API documentation\n- **[Docker Deployment Guide](docs/Docker.md)** - Container setup and examples\n- **[Configuration Examples](config/config.default.yml)** - Server/client/queue config\n- **[Rules Examples](config/rules.default.yml)** - Comprehensive rule examples\n\n---\n\n## Migration from v0.3.x\n\nqbt-rules v0.4.0 introduces breaking changes:\n\n### Key Changes\n\n1. **Distribution**: PyPI package → Docker images (ghcr.io)\n2. **Architecture**: Standalone CLI → Client-server with HTTP API\n3. **Terminology**: `trigger` → `context` (backward compatible flags exist)\n4. **Execution**: Direct execution → Queue-based job processing\n\n### Migration Steps\n\n1. **Rules File**: No changes needed (rules.yml syntax unchanged)\n2. **Config File**: Update structure (see config.default.yml)\n3. **Deployment**: Replace cron/systemd with Docker Compose + webhooks/cron container\n4. **CLI Usage**: Update to use HTTP API or client mode\n\n### Backward Compatibility\n\n- Rules syntax unchanged (only `trigger:` → `context:`)\n- CLI flag `--trigger` mapped to `--context` automatically\n- PyPI package v0.3.x remains available (deprecated)\n\n---\n\n## Requirements\n\n- **Docker** and **Docker Compose**\n- **qBittorrent v4.1+** with Web UI enabled\n- **Optional:** Redis server (for high-performance queue backend)\n\n---\n\n## Changelog\n\nSee [CHANGELOG.md](CHANGELOG.md) for version history and release notes.\n\n**Latest Release:** [v0.4.0](https://github.com/andronics/qbt-rules/releases/tag/v0.4.0) - 2024-12-14\n\n---\n\n## Contributing\n\nContributions are welcome! Please:\n\n1. Fork the repository\n2. Create a feature branch (`git checkout -b feature/amazing-feature`)\n3. Commit your changes (`git commit -m 'feat: add amazing feature'`)\n4. Push to the branch (`git push origin feature/amazing-feature`)\n5. Open a Pull Request\n\n---\n\n## License\n\nThis project is released into the **public domain** under the [Unlicense](http://unlicense.org/).\n\nYou are free to use, modify, and distribute this software for any purpose without attribution.\n\n---\n\n**Happy automating! 🚀🐳**\n\nFor complete documentation, see [docs/](docs/).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fandronics%2Fqbt-rules","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fandronics%2Fqbt-rules","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fandronics%2Fqbt-rules/lists"}