Stewarrd
Stewarrd brings qBittorrent, RDT-Client, SABnzbd and RQBit together in one authenticated web interface and API, alongside the Sonarr and Radarr instances that use them. It shows what every download is doing, explains why one is stuck, and can repair common failures once you have reviewed what it would do.
Documentation · Automation guide · Contributing · Security · MIT license
What it does
- Manage downloads. Search, filter and sort downloads across any number of client instances, with each client's native status and, for RDT-Client, provider and local progress shown separately. Pause, resume, retry or recheck where the client supports it. Protect a download to keep automation away from it. Every action is recorded, and an action whose outcome is uncertain must be reviewed before another is sent.
- Monitor Sonarr and Radarr. Follow their queues and health checks, and notice failed downloads and blocked imports.
- Recover failures. Replace releases that RDT-Client reports as failed, and, if you opt in, downloads that have stalled. Recovery blocklists the release in Arr, removes it from the client while keeping its files, and searches for a replacement within daily limits.
- Repair stuck imports. Copy-import Sonarr and Radarr downloads that are blocked only because Arr could not match them to an ID.
- Clean up unsafe content. Remove downloads containing executables or other unsafe files, after ownership, sharing and protection checks.
- Notify. Send each problem, and its resolution, to a JSON webhook.
Every recovery, import and cleanup policy starts in preview: it records what it would do without doing it. Stewarrd does not choose media for Arr, transfer files itself, enforce seeding rules, clean up orphaned files, or manage hosts and containers. The automation guide describes each policy's requirements and limits.
Install with Docker Compose
You need Docker Engine with BuildKit, Docker Compose v2, an x86-64 Linux host, and network access from the container to your clients and Arr applications.
git clone https://github.com/jamtur01/stewarrd.git
cd stewarrd
cp .env.example .env # add the credentials for the connections you will use
docker compose up -d --build
docker compose exec stewarrd cat /data/access-token
Open http://localhost:8787 and sign in with that token. Keep it private: it authorizes
both the web interface and the API. The included Compose file builds the checked-out
source, listens on loopback only, runs read-only as UID/GID 1000 with no added
capabilities, and keeps the token and SQLite database in the stewarrd-data volume.
Prebuilt images are published to Docker Hub.
To use one, replace build: . and image: stewarrd:local in compose.yaml with
image: jamtur01/stewarrd:<version>, then run docker compose pull && docker compose up -d.
Connect clients and Arr applications
- In Connections, add each client instance: its type, a unique instance ID, a display name, and a base URL the Stewarrd container can reach. Enter the username where needed and the name of the environment variable that holds its password or API key.
- Use Test & refresh to confirm the connection.
- In Media, add Sonarr and Radarr the same way, then map each download client to the exact name it has in that Arr.
- Check that the queues look right before adding any Recovery, Imports or Cleanup policy.
Credentials
Credentials live in .env, never in the connection form. Each variable must start with
STEWARRD_CLIENT_ and have a companion _URL variable that matches the connection's base
URL exactly, including any reverse-proxy path. Stewarrd refuses to send a credential to
any other address.
STEWARRD_CLIENT_SAB_API_KEY=replace-with-your-api-key
STEWARRD_CLIENT_SAB_API_KEY_URL=http://sabnzbd:8080
Here the form takes STEWARRD_CLIENT_SAB_API_KEY and http://sabnzbd:8080. After editing
.env, run docker compose up -d --force-recreate; a plain restart does not reload it.
Networking
Container names resolve only on a shared Docker network. If your clients run in a separate
Compose project, join Stewarrd to their network or use hostnames and published ports it can
reach; localhost inside the container is Stewarrd itself. If a client's reverse proxy
validates Host headers, configure its external host and port rather than turning the check
off.
Client notes
- RDT-Client: connect to its native API, not its qBittorrent-compatible endpoint. Its Retry action deletes the local files and the provider's copy before re-adding the torrent, so it asks for confirmation and requires cleanup storage mappings, the original torrent or magnet, and provider auto-import turned off. See native controls.
- RQBit: connect to its native HTTP API, normally on port 3030. Leave the credentials blank if the API has no authentication, or use the username and password fields for HTTP Basic authentication. It supports pause, resume, retry of failed jobs, stalled recovery, cleanup and copy-import recovery, but has no recheck. Map its storage before enabling cleanup on any client that shares that storage.
Automation
Recovery, Imports and Cleanup each have per-client or per-Arr policies. A new policy is in preview: it reads client and Arr state and records what it would do, but sends nothing. Enabling a policy authorizes only that policy's documented writes. Manual controls in the interface always act immediately; there is no global dry-run switch.
| Policy | When enabled, it | Also needs |
|---|---|---|
| Recovery | Blocklists failed or stalled releases, removes them from the client while keeping files, and searches for replacements | Read-only mounts of the Arr databases, plus RDT-Client's for RDT clients, and a network health endpoint |
| Imports | Copy-imports downloads Arr blocked only for an ID match | Read-only download mounts |
| Cleanup | Removes downloads with unsafe files and rejects the release in Arr | Read-only download mounts covering every client that shares storage, and the RDT-Client database for RDT |
Add the mounts to the Compose service, keeping them read-only, for example:
volumes:
- stewarrd-data:/data
- /srv/downloads/complete:/downloads/complete:ro
- /srv/rdt-client/db:/evidence/rdt:ro
- /srv/sonarr/config:/evidence/sonarr:ro
Follow the automation guide for mappings, prerequisites and exactly what each policy does. Run only one automation tool against the same downloads at a time.
Notifications
Set STEWARRD_NOTIFICATION_URL to deliver these as JSON webhook events (see the
automation guide for the payload). Each title names what
happened and to which download; the detail names the client and Arr and gives the reason.
Most conditions notify only after they persist for the delay shown, and send one event when
they start. When a condition ends, a second event prefixes the title with Cleared: (the
problem resolved) or Monitoring stopped: (Stewarrd stopped watching it: a recovery plan
was discarded or took over, a policy was turned off, or a client or Arr was removed).
| Notification | After | What it means | What to do |
|---|---|---|---|
<client> is unreachable |
15 min | Stewarrd cannot list that client's downloads. | Check the client is running and its URL and credentials. |
<Arr> is unreachable |
15 min | Stewarrd cannot read that Sonarr or Radarr. | Check the Arr is running and its URL and API key. |
<Arr> health <type>: <check> |
5 min | The Arr's own System → Status check reports a notice, warning or error, for example about an indexer or download client. | Open System → Status in that Arr. Silence a check with STEWARRD_NOTIFICATION_IGNORE_HEALTH. |
<Arr> download failed: <release> |
15 min | The Arr's queue marks the download failed; the detail gives the Arr's reason. Arr normally removes it and searches again itself. | Look at the Arr queue if it does not move on. |
<Arr> import blocked: <release> |
30 min | The download finished but the Arr cannot import it, for example a name it cannot match or no eligible files. | Fix the reason given, or import it manually in the Arr. |
<Arr> flagged unsafe content: <release> |
Immediately | The Arr's own check found a potentially dangerous file, such as an executable. | Remove the download; enabled cleanup does this for Arr-grabbed downloads. |
<client> download failed: <release> |
15 min | The client reports the job failed, with its error. Not sent for SAB, whose failures Arr handles, or for downloads an enabled recovery plan is handling. | Retry or remove it in the client. |
<client> download stalled: <release> |
Stall window | Stall recovery is on for that client but deliberately skips this transfer: a private torrent, qBittorrent before 5.0, or an unreadable RQBit torrent file. | Decide by hand; Stewarrd will not remove it. |
Recovery held: <release> |
15 min | A failed-release, stall or empty-record recovery plan stopped before finishing; the detail says why, for example Remove Failed is on, grab history is ambiguous or RDT auto-import is on. | Fix the cause and the plan continues, or review it in the recovery journal. |
Recovery checks failing: <client> → <Arr> |
15 min | Recovery cannot read that client, Arr, health endpoint or evidence database, so its stall timers restart. | Check connectivity and the read-only mounts. |
Import recovery held: <release> |
15 or 30 min | Copy-import recovery stopped on a safety check; the detail says which. | Fix the cause or import it manually. |
Import recovery checks failing: <Arr> |
15 min | Copy-import recovery cannot read that Arr or its downloads. | Check the Arr connection and download mounts. |
Unsafe content found: <release> |
Immediately, once | Cleanup found unsafe files in a download, whether cleanup is enabled, in preview, or the download was not grabbed by an Arr. | Review it under Cleanup. |
Unsafe download returned: <release> |
Immediately | A download cleanup already removed is back in the client with unsafe files; cleanup handles it again. | Check why it was grabbed again. |
Cleanup held: <release> |
Immediately | Cleanup will not, or cannot yet, remove this download; the detail says why, for example a shared payload, protection or RDT auto-import. | Fix the cause and cleanup retries next cycle. If the detail says a removal is unverified, review it under Cleanup. |
Unsafe download removed: <release> |
When done | Cleanup removed the download and rejected the release in the Arr. | Nothing. |
Cleanup checks failing: <Arr> queue |
15 min | Cleanup cannot read that Arr's queue and history. | Check the Arr connection. |
Cleanup checks failing: <client> files |
15 min | Cleanup cannot inspect that client's files. | Check the client and its download and evidence mounts. |
Configuration
| Setting | Default | Purpose |
|---|---|---|
STEWARRD_BIND_ADDRESS |
127.0.0.1 |
Compose host address; use your LAN address for LAN access |
STEWARRD_PORT |
8787 |
Compose host port |
PORT |
8787 |
Internal server port; update the container-side port mapping if changed |
STEWARRD_DATA_DIR |
/data in Docker |
Persistent application state |
STEWARRD_ACCESS_TOKEN |
Generated in the data directory | Optional operator/API token, at least 32 characters |
STEWARRD_SECURE_COOKIE |
false |
Set to true when the application is served over HTTPS |
STEWARRD_NOTIFICATION_URL |
Unset | Generic JSON webhook; no delivery when unset |
STEWARRD_NOTIFICATION_IGNORE_HEALTH |
Unset | Comma-separated Arr health source names excluded from notifications |
STEWARRD_DOWNLOADS_DIR |
/downloads |
Root for explicitly mounted download paths |
STEWARRD_EVIDENCE_DIR |
/evidence |
Root for read-only evidence database directories |
Running in production
- Remote access: put Stewarrd behind an HTTPS reverse proxy and set
STEWARRD_SECURE_COOKIE=true. Never expose the unencrypted login to the internet. - One instance per data volume. With a bind mount instead of the named volume, make the directory writable by UID/GID 1000. Keep download and evidence mounts read-only: clients make their own changes through their APIs, and Stewarrd needs no Docker socket or elevated privileges.
- Health:
GET /healthzneeds no login. It returns 503 when no polling cycle has completed in 10 minutes, so the container health check and an uptime monitor catch a hung polling loop or failing notification delivery. A worker that fails within a cycle is logged and reported through its own notifications instead.
Upgrades, backups and rollback
Stop Stewarrd before backing up its data volume, and keep the whole volume, including
access-token, together with your Compose and .env files. Keep backups private, and
never run docker compose down --volumes unless you mean to discard everything.
Read the release notes before upgrading. Older credential names must be renamed to
STEWARRD_CLIENT_*, given _URL bindings, and updated in saved connections. Build or pull
the chosen version, recreate the container, and check the connections with policies in
preview.
To roll back, stop the service and restore both the previous version and the data backup taken with it; an older version may not read newer state. Changing the access token and recreating the service rotates access, and any restart signs out existing browser sessions.
Troubleshooting
- Connection unavailable: test the URL from inside the Docker network, then check the
credential variable names, their
_URLbindings and the client's authentication log. - "Set …_URL": add the matching
_URLvariable and recreate the container. - Unknown action outcome: check the client itself, then acknowledge the action in Activity. An accepted API call does not prove the requested result.
- Cleanup held: check the journal and storage mappings. Every connected client must be mapped before Stewarrd can prove a payload is not shared.
- Import still blocked: only specific ID-match failures qualify for copy import; permission, quality and ambiguous matches need your review.
- Logs:
docker compose logs --since 30m stewarrd. Remove credentials, signed URLs and personal paths before sharing them.
API and development
API clients send Authorization: Bearer <access-token>. The authenticated
GET /api/openapi.json documents the same operations the interface uses.
Stewarrd is built with Node 24, TypeScript, React, Fastify and SQLite. See CONTRIBUTING.md for local development and checks, and SECURITY.md to report a vulnerability privately. The project is MIT licensed; dependencies keep their own licenses.