transpondarr

module
v0.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 1, 2026 License: Apache-2.0

README

Transpondarr

An anime-focused PVR — Sonarr's job, built around anime-native tooling and metadata. It monitors series, finds releases on anime indexers, drives a download client, and organizes the results into a media library.

Status: Beta. The acquisition loop runs end-to-end and unattended — add a series from AniList, and monitored episodes are searched, graded against your quality profile, grabbed via qBittorrent, and hardlinked into a Plex/Jellyfin-ready library. Automation ships off by default; flip it on in Settings. Indexing is via Torznab/Prowlarr for now.

Why not just use Sonarr?

Anime breaks Sonarr's assumptions: messy fansub filenames, absolute vs. per-season numbering, release-group/dual-audio/sub preferences, and metadata that lives on AniList/AniDB rather than TVDB.

Features

Today:

  • AniList-native metadata — add series from AniList search, browse a seasonal discovery chart, and see upcoming episodes on an airing calendar keyed to Japanese broadcast times.
  • Automated acquisition — a scheduled sweep searches monitored series once their episodes have aired, grabs eligible releases, and imports them; per-series monitoring and a global on/off switch (off until you enable it).
  • Anime-aware quality profiles — release group is the dominant axis, then resolution/source, dual audio, and sub preferences, with a score floor and hard excludes. A per-series pinned group can also mean wait for — hold new episodes for the pinned group's release before settling for another.
  • Failure memory — a failed release is blocklisted with escalating expiry instead of re-grabbed forever, an environmental-fault breaker stops one bad afternoon from blocklisting the library, and everything is visible and unblockable in the UI.
  • Manual control that's never refused — search and grab any release by hand; profiles inform manual actions but only gate automation.
  • Seeding-safe library import — hardlink (or copy) into Plex/Jellyfin-ready naming, without breaking the seeding torrent.
  • Self-hosted, single binary — embedded web UI, login + API key auth, REST API with an OpenAPI spec, observable background jobs, and live-editable settings — no restarts.

Planned (tracked in the milestones):

  • Recent-feed polling, so new releases are grabbed within minutes of appearing rather than on the next sweep (in flight).
  • Notifications (Discord, webhook, ntfy) and a global activity feed.
  • Batch/season-pack import, quality upgrades, and episode-targeted search.
  • Post-1.0: anime movies, AniList account sync (auto-monitor your Watching list), importing a pre-existing library, multiple indexers, and Sonarr-API compatibility for existing dashboard/mobile apps.

Install

Transpondarr ships as a single static binary with the web UI embedded — no separate database or frontend to run.

Docker

Pull the published multi-arch image (or build locally with docker build -t transpondarr .):

docker pull ghcr.io/matthewdias/transpondarr:latest
docker run -p 9797:9797 -v ./config:/config ghcr.io/matthewdias/transpondarr:latest

The container starts as root only to fix ownership of the mounted config dir, then drops to PUID/PGID (default 1000:1000) before serving. To skip the root phase entirely, run with --user "$(id -u):$(id -g)" — the config dir must then already exist and be writable by that user.

For a real deployment alongside qBittorrent and a media server, see Docker deployment below.

Binary

Download the archive for your platform from the releases page, extract it, and run ./transpondarrd.

To build from source instead, see CONTRIBUTING.md.

First run

The server listens on :9797. The web UI uses a login (username + password): on first run you create an admin account, or set TRANSPONDARR_AUTH_USERNAME/ _PASSWORD to bootstrap one. A separate API key guards /api/* for machine clients (dashboards, scripts, a future HA integration) via the X-Api-Key header — it's generated and persisted on first run and shown in Settings → API access (set TRANSPONDARR_API_KEY to pin one). Health check (public):

curl localhost:9797/api/v1/health

Configuration

Integrations are set through TRANSPONDARR_* environment variables or edited at runtime in the Settings UI (those DB overrides take precedence over the environment and apply live, without a restart). Unset, unconfigured integrations are simply disabled — the server still starts.

Variable Default Purpose
TRANSPONDARR_API_KEY (generated + persisted) Machine-client key for /api/* (X-Api-Key). Auto-generated and saved in the DB; set to override.
TRANSPONDARR_AUTH_USERNAME / _PASSWORD Bootstrap the initial web-UI admin account on first run (otherwise use the setup screen).
TRANSPONDARR_AUTH_REQUIRED enabled enabled (always require login) | local (skip login for local/private addresses).
TRANSPONDARR_ADDR :9797 Listen address.
TRANSPONDARR_DATA_DIR ./data SQLite DB + state (/config in Docker).
TRANSPONDARR_DB <DATA_DIR>/transpondarr.db SQLite DB file path. Override to relocate the DB independently of the data dir.
TRANSPONDARR_QBIT_URL qBittorrent WebUI root; unset ⇒ no download client.
TRANSPONDARR_QBIT_USER / _PASSWORD qBittorrent credentials.
TRANSPONDARR_QBIT_CATEGORY transpondarr Category applied to grabbed torrents.
TRANSPONDARR_TORZNAB_URL Torznab feed (Prowlarr/Jackett); unset ⇒ no indexer.
TRANSPONDARR_TORZNAB_APIKEY Torznab API key.
TRANSPONDARR_TORZNAB_NAME torznab Display name for the indexer.
TRANSPONDARR_LIBRARY_DIR Library root for imports; unset ⇒ import disabled.
TRANSPONDARR_IMPORT_MODE auto auto (hardlink, copy across filesystems) | hardlink | copy.
TRANSPONDARR_AUTOMATION_ENABLED false Let the feed poll and the scheduled sweep grab unattended.
TRANSPONDARR_PIN_DELAY_HOURS 0 Hours automation waits for a series' pinned group before taking another; per-series overrides in the UI.
PUID / PGID 1000 / 1000 Docker only: the uid:gid the container drops to after fixing /config ownership on start.

Auth & reverse proxies. The local auth mode skips login only for requests from loopback/private addresses with no forwarding headers, so reverse-proxied requests (which set X-Forwarded-For) always require login — a same-host proxy can't turn local-bypass into open access. Session cookies are marked Secure automatically when the proxy sets X-Forwarded-Proto: https.

Docker deployment

For a real deployment alongside qBittorrent and a media server, use docker-compose.yml as a template. Two things matter:

  • Imports hardlink from the path qBittorrent reports. Mount your shared downloads/library volume into Transpondarr at the same path qBittorrent uses, with both on one filesystem (a hardlink can't cross filesystems). The standard single-mount layout (/data/torrents + /data/media) satisfies this.
  • Ownership. Set PUID/PGID to the UID:GID that owns your media volume — the container starts as root, fixes /config ownership, and drops to that user before serving, so hardlinks into the library land with the right ownership. Persist the /config volume (it holds the SQLite DB).

Verify a running deployment (the second call needs your API key):

curl -s http://localhost:9797/api/v1/health                      # {"status":"ok",...}
curl -s -X POST -H "X-Api-Key: <key>" http://localhost:9797/api/v1/download/test
#   {"status":"ok","client":"qbittorrent"}   (502 => qBit URL/creds wrong)

Contributing

See CONTRIBUTING.md for the stack, toolchain, build-from-source steps, and codebase layout.

License

Apache-2.0. Third-party dependency licenses and notices are reproduced in THIRD-PARTY-NOTICES.md (regenerate with make notices).

Directories

Path Synopsis
cmd
transpondarrd command
internal
config
Package config loads runtime configuration from the environment with sane defaults.
Package config loads runtime configuration from the environment with sane defaults.
core/acquire
Package acquire owns search, decide, and grab, shared by the manual HTTP routes, the scheduled sweep and the feed poll so all three drive exactly one matcher.
Package acquire owns search, decide, and grab, shared by the manual HTTP routes, the scheduled sweep and the feed poll so all three drive exactly one matcher.
core/airing
Package airing keeps per-item broadcast times in step with the metadata provider, creating the wanted items a schedule names as it goes — for a long-runner whose episode total AniList never publishes, the schedule is the only source that knows those episodes exist.
Package airing keeps per-item broadcast times in step with the metadata provider, creating the wanted items a schedule names as it goes — for a long-runner whose episode total AniList never publishes, the schedule is the only source that knows those episodes exist.
core/auth
Package auth implements forms-based authentication: a single admin account (username + argon2id-hashed password) and opaque server-side login sessions carried in an httpOnly cookie.
Package auth implements forms-based authentication: a single admin account (username + argon2id-hashed password) and opaque server-side login sessions carried in an httpOnly cookie.
core/blocklist
Package blocklist is the pipeline's failure memory: which release of a series already failed, so the sweep stops re-deriving the same doomed ranking (#118).
Package blocklist is the pipeline's failure memory: which release of a series already failed, so the sweep stops re-deriving the same doomed ranking (#118).
core/browse
Package browse serves seasonal discovery charts from a per-season cache.
Package browse serves seasonal discovery charts from a per-season cache.
core/catalog
Package catalog is the entry point to Transpondarr's tracked-title collection.
Package catalog is the entry point to Transpondarr's tracked-title collection.
core/clients
Package clients holds the live download/indexer/library clients behind a mutex so the settings layer can rebuild and swap them at runtime — a config change takes effect without restarting the process.
Package clients holds the live download/indexer/library clients behind a mutex so the settings layer can rebuild and swap them at runtime — a config change takes effect without restarting the process.
core/decide
Package decide is the "decide" stage of the pipeline: given a tracked Title's wanted items and a set of raw indexer releases, it works out which release satisfies which item.
Package decide is the "decide" stage of the pipeline: given a tracked Title's wanted items and a set of raw indexer releases, it works out which release satisfies which item.
core/domain
Package domain holds Transpondarr's content-type-agnostic core model.
Package domain holds Transpondarr's content-type-agnostic core model.
core/download
Package download defines the pluggable download-client interface.
Package download defines the pluggable download-client interface.
core/download/qbittorrent
Package qbittorrent implements the download.Client interface against qBittorrent's WebUI API by wrapping the maintained autobrr/go-qbittorrent client, which owns the login/CSRF/session handshake and cross-version quirks (and re-logins automatically on session expiry).
Package qbittorrent implements the download.Client interface against qBittorrent's WebUI API by wrapping the maintained autobrr/go-qbittorrent client, which owns the login/CSRF/session handshake and cross-version quirks (and re-logins automatically on session expiry).
core/importer
Package importer is the final pipeline stage: it watches the download client for grabs Transpondarr initiated (rows in the grabs table) and, once a torrent completes, hands the file to a library.Target and marks the item had.
Package importer is the final pipeline stage: it watches the download client for grabs Transpondarr initiated (rows in the grabs table) and, once a torrent completes, hands the file to a library.Target and marks the item had.
core/indexer
Package indexer defines the pluggable indexer interface.
Package indexer defines the pluggable indexer interface.
core/indexer/torznab
Package torznab implements the Indexer interface against a Torznab-compatible endpoint (e.g.
Package torznab implements the Indexer interface against a Torznab-compatible endpoint (e.g.
core/jobs
Package jobs runs named background work on a fixed interval, in memory.
Package jobs runs named background work on a fixed interval, in memory.
core/library
Package library defines the final stage of the import pipeline: the LibraryTarget interface.
Package library defines the final stage of the import pipeline: the LibraryTarget interface.
core/library/mediaserver
Package mediaserver implements the library.Target interface by placing files into a Jellyfin/Plex-friendly layout:
Package mediaserver implements the library.Target interface by placing files into a Jellyfin/Plex-friendly layout:
core/metadata
Package metadata defines the pluggable metadata-provider interface.
Package metadata defines the pluggable metadata-provider interface.
core/metadata/anilist
Package anilist implements metadata.Provider against AniList's public GraphQL API (https://graphql.anilist.co).
Package anilist implements metadata.Provider against AniList's public GraphQL API (https://graphql.anilist.co).
core/metadata/dbcache
Package dbcache implements metadata.Cache on top of the SQLite metadata_cache table (via the sqlc layer).
Package dbcache implements metadata.Cache on top of the SQLite metadata_cache table (via the sqlc layer).
core/parser
Package parser turns a raw release title into structured fields — title, season, episode number(s), release group, and the quality axes (resolution, source, subtitle type, codec, version/repack, dual-audio) — using anitogo (a Go port of Anitomy): anime filename conventions are a large heuristic problem better handled by a maintained library than by hand.
Package parser turns a raw release title into structured fields — title, season, episode number(s), release group, and the quality axes (resolution, source, subtitle type, codec, version/repack, dual-audio) — using anitogo (a Go port of Anitomy): anime filename conventions are a large heuristic problem better handled by a maintained library than by hand.
core/refresh
Package refresh grows a tracked series as its provider metadata moves: a releasing title whose episode count rises (or arrives, having been unknown at add time) gains the missing wanted items on the next pass.
Package refresh grows a tracked series as its provider metadata moves: a releasing title whose episode count rises (or arrives, having been unknown at add time) gains the missing wanted items on the next pass.
core/settings
Package settings is the runtime-configuration layer.
Package settings is the runtime-configuration layer.
coretest
Package coretest provides shared test doubles and fixtures for exercising the acquisition pipeline (search → decide → grab → import) end to end.
Package coretest provides shared test doubles and fixtures for exercising the acquisition pipeline (search → decide → grab → import) end to end.
privdrop
Package privdrop lets the container start as root just long enough to fix ownership of the data directory, then sheds privileges — the in-binary equivalent of the PUID/PGID init step in linuxserver-style images, needed because the distroless image has no shell or init to do it externally.
Package privdrop lets the container start as root just long enough to fix ownership of the data directory, then sheds privileges — the in-binary equivalent of the PUID/PGID init step in linuxserver-style images, needed because the distroless image has no shell or init to do it externally.
server
Package server wires the HTTP layer: a chi router, the Huma (OpenAPI 3.1) API, API-key auth, and the embedded single-page frontend.
Package server wires the HTTP layer: a chi router, the Huma (OpenAPI 3.1) API, API-key auth, and the embedded single-page frontend.
store
Package store owns the SQLite database: connection, embedded goose migrations, and the sqlc-generated query layer (internal/store/db).
Package store owns the SQLite database: connection, embedded goose migrations, and the sqlc-generated query layer (internal/store/db).
version
Package version exposes the build version, overridable via -ldflags at build time (see .goreleaser.yaml and the Makefile).
Package version exposes the build version, overridable via -ldflags at build time (see .goreleaser.yaml and the Makefile).
Package web embeds the built frontend (web/dist) into the binary so Transpondarr ships as a single artifact.
Package web embeds the built frontend (web/dist) into the binary so Transpondarr ships as a single artifact.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL