|
Some checks are pending
CI / check (push) Waiting to run
CI / deny (push) Waiting to run
CI / plan-file-gate (push) Waiting to run
release-artifacts / appimage (push) Waiting to run
release-artifacts / deb (push) Waiting to run
release-artifacts / docker (push) Waiting to run
release-artifacts / publish (push) Blocked by required conditions
PlexWatchlistProvider fetches user's Plex watchlist via metadata.provider.plex.tv, parses XML Video/Directory elements with Guid children for TMDB/TVDB/IMDB external IDs (12 unit tests). Exclusion API: GET/POST/DELETE /api/v3/importlistexclusion with validation (4 unit tests). WebUI SettingsImportLists.tsx: full CRUD for both lists and exclusions with per-implementation form fields. ImportListSyncTask auto-add candidate detection logs items not yet in library. CLI importlist-exclusion list/delete. Scheduler tests updated for 9-task registry (ImportListSync added). [gstack-context] milestone-cycle m148 |
||
|---|---|---|
| .github/workflows | ||
| .sqlx | ||
| crates | ||
| docs | ||
| packaging | ||
| resources | ||
| scripts | ||
| tests | ||
| .cgcignore | ||
| .dockerignore | ||
| .gitignore | ||
| Cargo.lock | ||
| Cargo.toml | ||
| CHANGELOG.md | ||
| CLAUDE.md | ||
| CONTRIBUTING.md | ||
| deny.toml | ||
| docker-compose.yml | ||
| Dockerfile | ||
| LICENSE | ||
| README.md | ||
| ROADMAP.md | ||
| rust-toolchain.toml | ||
| THANKS.md | ||
| TODOS.md | ||
This is the
mainbranch — active development (v0.55.0+, SQLite-backed).For the v0.42.0 legacy line (JSONL + TOML persistence), check out the
legacybranch. Both branches share crate names, binary names, and the~/.config/avatarr/config file — run only one version at a time, and back up~/.local/share/avatarr/before cross-version experiments.
🎬 Avatarr
A single-binary Rust media manager. Search torrents, download via qBittorrent, organize into a Plex-compatible library — GUI and CLI in one portable executable.
Working toward a leaner, unified replacement for the Sonarr + Radarr stack: TV and movies in one binary, one database, three UI surfaces (Slint desktop GUI for power users, React WebUI for homelab/Docker, avatarr CLI for headless). Phase A of the rewrite arc (m40 rename → m133 distribution + signed releases) is shipped; the Phase B parity extension arc (m135–m154 — authored by the 2026-06-28 m134 parity audit, restructured by the 2026-07-01 core-loop audit) is in progress. See ROADMAP.md for the friendly status mirror, or docs/plans/2026-04-30-avatarr-roadmap-post-m53b-redraft.md for the strategic spec with full reasoning + decision log. The 2026-05-25 post-m108 drift audit (docs/audit-2026-05-25-m108-drift.md) restructured the original m109-m113 polish arc into m109-m130, extended to m109-m133 by the 2026-06-09 librarification rewrite (componentization arc: avatarr-media extraction + boundary ledger + gated crates.io publish checkpoint + library hardening) — see ROADMAP for the current arc layout. Earlier 2026-04-21 + 2026-04-11 roadmaps are preserved for historical context.
Design reference: visual, interaction, and information design for the unified rebuild lives at docs/design/DESIGN.md (v1 handoff). The bundled HTML mockups under docs/design/hifi/ are pixel reference — implementation recreates them in Slint rather than lifting code. Start with docs/design/README.md for onboarding order.
Renamed from
plex-pipeline-rsin v0.40.0 (April 2026). The project migrated to fresh repos at https://codeberg.org/alan090/avatarr and https://github.com/alan09086/avatarr. The oldplex-pipeline-rsrepos have been archived. See CHANGELOG.md for the migration entry.
🧭 Status
Today (v0.70.0):
- Working torrent search across TPB, YTS, EZTV, and RuTracker with score-based ranking
- qBittorrent monitor that auto-organizes completed downloads into Plex folder structures
- Movies + TV (seasons, episodes, ranges, complete-series packs)
- Explicit Movie/TV mode selector in the Search tab — no more guessing what the parser will infer
- TV Season Picker dialog when a bare TV title is searched — TMDB resolves the canonical title, the picker offers S01–S10 or a Complete Pack shortcut
avatarr search --type {movie,tv}is strict;--completeand--season Nprovide direct-dispatch shortcuts for TV- Smart per-episode quality upgrades (size + resolution comparison)
- IMDB/TMDB metadata resolution with year-aware caching
- clap-based CLI, Slint desktop GUI (m71-m85 rebuild), and React WebUI (m92+)
- Cross-platform: Linux, Windows, macOS
- SQLite catalog foundation (m43): hardened sqlx pool, 49-table unified schema from Sonarr v4 + Radarr v6, automatic migrations at startup, fatal-error exit with actionable messages on pool-open failure
- Series / Season / Episode domain models (m44): typed Rust structs mirror the schema column-for-column; enums with
Unknown(i32)fallthrough round-trip unknown DB values without erroring;SeriesRepository/SeasonRepository/EpisodeRepositoryas#[async_trait]+ object-safe surfaces;Episode::has_file()+is_missing(now)computed helpers. Migration 0002 triggers keep the legacySeries.SeasonsJSON column coherent with theSeasonsTABLE by construction inside the triggering transaction. - Movie / MovieMetadata / MovieFile domain models (m45): Movies side of the port — 18-field
Moviewith schema-mirrored denormalisation, 29-field content-sideMovieMetadatabound viaMovieMetadataIdFK, andMovieFilewithhas_media_info()helper.MovieStatusType(Deleted=-1,Tba=0,Announced=1,InCinemas=2,Released=3),MovieMonitorType,AddMovieMethodenums withUnknown(i32)fallthrough.MovieRepository/MovieMetadataRepository/MovieFileRepositoryas#[async_trait]+ object-safe surfaces with sqlx-backed impls. Migration 0003 triggers keepMovies.HasFile/Movies.MovieFileIdin sync with theMovieFilestable on every INSERT/UPDATE/DELETE. - Repository layer polish + path newtypes + remote-path resolver (m46):
IBasicRepository<T, NewT>supertrait bundlesfind_by_id/insert/update/delete_by_idacross Series/Episode/Movie/MovieFile — each entity repo now inherits the shared CRUD surface instead of redeclaring it (Season + MovieMetadata stay onupsert, so they opt out). 6 strongly-typed path newtypes (SeriesPath,MoviePath,MovieFilePath,RelativePath,LocalPath,RemotePath) replace rawString/PathBufon the TV + Movie domain structs.RemotePathMapping+SqliteRemotePathMappingRepository+RemotePathResolvertranslate between remote and local path namespaces using longest-prefix-match withPath::components()normalization and a TTL +AtomicU64version-counter invalidated cache (4 error variants,..escape-path rejection). No new migration —RemotePathMappingstable was already declared in0001_initial.sql. - MoveLogs SQLite migration + dual-writer (m47): filesystem-move audit trail migrates from JSONL-only to a SQLite + JSONL dual-writer over a 9-release window (v0.47.x → v0.55.x). Typed
MoveLog/NewMoveLog/FileMovedomain models withMoveLogStatus/MoveLogCategoryenums carrying#[serde(other)] Unknownfor forward-compat.MoveLogWritertrait abstraction withDualWriter(JSONL first — rollback-safe, then SQLite — warn-on-error) andSpyWriterfor tests. Startup sentinel probe (transaction-rollbackINSERT) catches broken write paths at boot (CLI exit code 3). Periodic parity check every 100 writes logserror!on drift beyond ±2 rows. Migration0004_move_logs.sqlships theMoveLogstable + 3 indexes; SHA-256 pinned. JSONL writer removal is scheduled for v0.56.0. - RootFolder domain + repository (m48):
RootFolder(12 fields) andNewRootFolder(11 fields) domain types mapping theRootFolderstable.RootFolderRepositorytrait extendsIBasicRepositorywithfind_by_path(UNIQUE path, byte-equal) andfind_all(insertion-order).SqliteRootFolderRepositorywith privateRootFolderRow+Fromboundary pattern. No new migration — table was declared in0001_initial.sql. - Parser baseline measurement (m49): Sonarr v4.0.17.2952 parser fixture corpus (1,416 cases, 12 categories) vendored at
tests/sonarr-fixtures/.crates/parser-baseline/harness benchmarks hunch v2.0.0 and the existing bespoke parser side-by-side. Verdict: path C — port Sonarr's QualityParser.cs + LanguageParser.cs (hunch 30.3% / bespoke 0.9% weighted, both below the 60% fork threshold). Report atdocs/research/hunch-baseline.md. m50 reframes as Sonarr parser port. - Sonarr parser port (m50):
crates/parser/workspace member with full GPL-3.0 port of Sonarr v4.0.17.2952'sQualityParser.cs(15 regexes, 21Qualityvariants via Resolution + QualitySource cascade) andLanguageParser.cs(6 regexes, 47Languagevariants). avatarr-parser scores 94.5% quality / 100.0% language on the fixture corpus, well above the m51 ≥70% gate. 4-way bake-off incrates/parser-baseline/(Sonarr ground-truth + hunch + bespoke + avatarr-parser). Library only at m50; m51 wires into release matching.scripts/build-appimage.shrestored. Report atdocs/research/m50-port-baseline.md. - Episode regex cascade + release-group pipeline (m52-m53b): 89 Sonarr
ReportTitleRegex[]cascade entries ported — 42 standard (m52), 9 daily, 34 absolute (m53). Post-cascade release-group pipeline hand-rolls Sonarr'sParseReleaseGroup(anime[SubGroup], exception lists, trailing-GROUP). Fixture pass rates: single 171/171, multi 69/69, season 49/49, daily 42/42, absolute 155/155 (100%), release_group 112/112. m53b closed the absolute fixture gap via mask-and-retry lookahead emulation, batch-range fallback, dual-capture range routing, trailing-year reattachment, and post-handler tail enrichment.extract.pyfixture extractor fixed for paren-balancing and comment-skipping. - Series add + TVDB resolver (m61):
avatarr series searchqueries TheTVDB for series by name;avatarr series add --tvdb-id N --path /tv/showfetches metadata and atomically inserts Series + Seasons + Episodes in a single transaction. 14-variant monitor strategy (--monitor all|none|future|first-season|…), root-folder default resolution,--jsonoutput for both commands. TVDB v4 API client with bearer-token caching and automatic 401 retry. - Movie add + TMDB resolver (m62):
avatarr movie searchqueries TMDB for movies by title (optional--yearfilter);avatarr movie add --tmdb-id N --path /movies/dirfetches TMDB metadata and atomically inserts MovieMetadata + Movie in a single transaction. Configurable--monitored,--minimum-availability(announced/in-cinemas/released), root-folder quality profile resolution,--jsonoutput for both commands. Dedicated TMDB v3 API client with query-param auth. - Monitor / unmonitor state machine (m63): runtime
MonitorServiceplus a CLI surface that toggles theMonitoredflag on Series, Season, Episode, and Movie rows.avatarr series monitor/unmonitor,avatarr series apply-monitor --strategy <STRATEGY>(cascades through every season + episode using the same 14-variant strategy as m61's add-time picker, now includingMonitorSpecials/UnmonitorSpecials),avatarr series set-monitor-new-items --mode all|none,avatarr series show/list,avatarr movie monitor/unmonitor,avatarr movie show/list,avatarr season monitor/unmonitor, andavatarr episode monitor/unmonitor(range syntax:1-5,1,3,5,1-3,7,10-12). Reposet_monitoredreturnsResult<usize, _>with aMonitored != ?guard to skip no-op writes;--dry-runon apply-monitor + episode commands distinguishes targeted rows from rows actually changing. - Missing detection + bulk-add (m64):
avatarr series missingandavatarr movie missingsurface monitored items without files.avatarr series bulk-add --file <path>andavatarr movie bulk-add --file <path>ingest JSON arrays for batch library population, with--dry-runlocal-DB validation and--jsonoutput. Final milestone of the Library Management cluster (m61-m64). - Scheduler subsystem (m65):
tokio-cron-scheduler-backed scheduler daemon withTaskRegistry,CommandExecutor(500ms-poll, atomic-claim), PID lockfile single-instance enforcement, exponential backoff on task failures.MediaInfoBackfillTaskas the first concrete task. CLI:avatarr scheduler run,avatarr scheduler list,avatarr commands list. - RSS polling + missing-search jobs (m66): Two new scheduled tasks that actively discover content.
RssPollTask(15-min default) polls all RSS-enabled Newznab/Torznab indexers, runs releases through the decision engine, and lands accepted results inPendingReleases.MissingSearchTask(6-hour default) searches indexers for monitored-but-missing episodes and movies (10 series / 20 movies per run). Sharedprocess_releasepipeline bridgesavatarr-parserquality parsing to the m59 decision engine.IndexerApitrait withNewznabClientHTTP client andMockIndexerApitest double.IndexerStatusbackoff with escalating cooldowns (5m → 24h). Migration 0006 adds per-indexerLastRssSynctimestamp,PendingReleases.GuidUNIQUE dedup index, and promotesIndexerStatus.IndexerIdto UNIQUE for correct UPSERT. CLI:avatarr pending list/clear,avatarr indexer list. Final milestone of the Scheduler cluster (m65-m66). - IDownloadClient trait + qBittorrent refactor (m67):
IDownloadClientasync trait abstracts download clients behindArc<dyn IDownloadClient>.QBitDownloadClientimplements qBittorrent Web API v2.DownloadClientProviderresolves protocol + client ID to the correct implementation with backoff awareness.DownloadClient/DownloadClientStatus/DownloadHistorydomain types with full repository layer.DownloadHistoryCleanupTask(24h) removes orphaned history rows. Monitor pipeline migrated from concrete&QbitClientto&dyn IDownloadClient. Oldqbit.rsdeleted. Migration 0007 promotesDownloadClientStatus.DownloadClientIdindex to UNIQUE. CLI:avatarr download-client list. First milestone of the Downloads + import cluster (m67-m70). - TrackedDownloads DB + broadcast event channels (m68):
TrackedDownloadstable (12 cols, migration 0008) persists downloads through their lifecycle (Downloading → ImportPending → Importing → Imported).TrackedDownloadStateenum withUnknown(i32)fallthrough.EventBus+DownloadEvent(Grabbed/StateChanged/Removed) ontokio::sync::broadcast— A1 architectural lock for all future streaming surfaces (Slint m71+, Axum SSE m92).GrabReleasesTask(1-min) dequeues pending releases to download clients with crash-recovery dedup viaexists_by_guid.DownloadSyncTask(1-min) polls clients for status.DownloadClientProvidercaches resolvedArc<dyn IDownloadClient>.MockDownloadClienttest double feature-gated for downstream consumers. CLI:avatarr queue list. - Hardlink import + recycle bin + RPM-aware paths (m69):
ImportServicedrivesTrackedDownloadrows throughImporting → (Imported | Failed)and broadcastsDownloadEvent::{Imported, ImportFailed}.place_file3-tier cascade — reflink (FICLONE vialibc::ioctl) → hardlink (std::fs::hard_link) → copy + size-verify — with stat-probe crash recovery (existing inode/size match short-circuits, mismatch returnsDestExistsrather than clobbering).RecycleBinServiceroutes upgrade-replaced files to<data_dir>/recyclebin/<YYYY-MM-DD>/with O(1) millisecond-suffix collision avoidance;RecycleBinRepository(append-only, no-update) backsrestore+purge_expired. 3 new scheduled tasks wired into the daemon:ImportPendingTask(1-min, capped 50/tick),CleanupRecycleBinTask(weekly),RecoverStuckImportingTask(10-min, flipsImportingrows wedged > 5 min back toImportPending). Migration 0009 addsRecycleBintable +TrackedDownloads.ContentPath(D2 — snapshot save_path onDownloading → ImportPendingso import doesn't depend on the torrent client still holding the row) +LastStateChangeAt(D8 — drives the stuck-importing recovery). Legacy push-driven pipeline deleted (~770 prod lines + 572 test lines):monitor::{process_completed, monitor_loop, process_torrent, process_movie, process_tv, cleanup_torrent}+organizer::{execute_movie, execute_tv}+ 3 CLI handler call sites. CLI:avatarr import run,avatarr import list-pending [--json],avatarr recycle list [--json] / restore <ID> / empty --yes. Third milestone of the Downloads + import cluster (m67-m70). - Indexer abstraction +
avatarr smokeself-test (m70):crates/core/src/indexer.rs(1114 LOC monolith) restructured into a directory moduleindexer/{mod.rs, newznab.rs, mock.rs}— peer of the m67download_client/layout. Trait surface unchanged;MockIndexerApipromoted from#[cfg(test)]to#[cfg(any(test, feature = "test-support"))]so release binaries opt into the test mocks. Three duplicateNullIndexerApistubs collapsed across the scheduler tests. Newavatarr smokeCLI subcommand runs six pipeline stages — parse → decide → mock-indexer → mock-download → hardlink → move-log — against atempfile::TempDir-backed pool and prints a green/red ANSI table. Exit 0 on all-green / 1 otherwise. Hermetic by construction: never touches the user's realAVATARR_DATA_DIR, so it runs on a fresh install with no config, no DB, no network. Closes D10 from the 2026-04-30 rebuild spec as the backend-phase exit validator. Final milestone of the Downloads + import cluster (m67-m70). - 1,766 tests across 74 binaries,
cargo clippy --workspace --all-targets --all-features -- -D warningsclean, full CLI binary smoke (17 subcommands / 28 probes) green - Auto-migration from
~/.config/plex-pipeline/→~/.config/avatarr/on first launch (idempotent, EXDEV-safe)
Phase A shipped through m133; Phase B parity arc (m135–m154) underway (solo part-time at ~0.6× intensity):
Weekly milestone cadence (each ships v0.N.0), grouped into thematic clusters per the 2026-04-30 redraft:
| Cluster | Milestones | Theme |
|---|---|---|
| Foundation | m43 → m48 ✅ | sqlx infra, domain models, repository layer, JSONL→SQLite migration, RootFolder |
| Parser quality | m49 → m53b ✅ | Sonarr fixture corpus + parser port + 100% fixture pass rate |
| Cleanup + parser arc | m54 → m57 | Legacy GUI deletion + binary migration to crates/cli, parser anime + year mismatch, parser wire-in, crates.io publish |
| Decision engine | m58 → m60 | Quality profiles + cutoff/upgrade + Accept/Reject/Upgrade engine |
| Library management | m61 → m64 | Series/Movie add workflows, monitor state machine, missing detection |
| Scheduler | m65 → m66 | tokio-cron-scheduler + RSS polling + missing-search |
| Downloads + import | m67 → m70 | IDownloadClient trait, tracked downloads, hardlink import, indexer abstraction, avatarr smoke end-to-end self-test |
| Slint desktop GUI rebuild | m71 → m85 | New crates/gui from DESIGN.md — tokens + 4-accent theme cycle + 22 components + 10 screens + cutover + soak |
| API v3 (Axum) | m86 → m93 | Load-bearing for both WebUI and Bazarr/Prowlarr; X-Api-Key auth, full endpoint coverage, OpenAPI spec |
| WebUI (React + TS) | m94 → m103 | Parallel implementation sharing tokens.toml with Slint, 10 screens mirroring DESIGN.md, Docker compose ship |
| Migration importer | m104 → m105 | avatarr-cli import {sonarr,radarr} with dry-run + idempotency |
| Notifications + health | m106 → m107 ✅ | Discord/Telegram/Gotify/webhook notifiers + health check framework + Codeberg update checker |
| Settings configuration | m108 ✅ | Full TOML config exposed in both GUIs via GET/PUT /api/v3/config |
| Test discipline | m109 | Fix 34 failing monitor_handlers tests (T1 violation on main), dead-code + stale-TODO audit, T1 plan-file CI gate. Post-audit insertion — see docs/audit-2026-05-25-m108-drift.md. |
| Componentization — library platform | m110 → m111 (+ CP-1 ⛔) | avatarr-media extraction (dual-badged medialint M1, executes the committed extraction plan), evidence-driven library boundary ledger, gated crates.io publish checkpoint (sign-off only, never loop-walked). 2026-06-09 librarification insertion. |
| Invariant closure | m112 | Close two m86 deferrals (DownloadClientProvider cache invalidation + QualityProfile handler serialisation), Sonarr-compat stub policy |
| Settings CRUD — API layer | m113 → m117 | POST/PUT/DELETE for Indexer, DownloadClient, RootFolder, QualityProfile, Notification (one entity per milestone). Closes the m81 "CRUD editing deferred" deferral that the audit found unresolved at m108. |
| Settings CRUD — WebUI management | m118 → m122 | Add/edit/delete forms in React Settings tabs, mirroring the API layer (one entity per milestone) |
| First-run + end-to-end QA | m123 → m126 | First-run wizard, search-to-import walkthrough QA, GUI dogfooding round 2 (82 empty-DB-deferred items), UX polish (empty states, loading, validation feedback) |
| Resilience | m127 | Network failure handling, disk-space exhaustion, SIGTERM graceful shutdown in scheduler + API server |
| Contributor + user docs + library hardening | m128 → m131 | CONTRIBUTING.md + dev-setup + per-crate READMEs, getting-started + Sonarr/Radarr migration guide, API + config reference, library hardening (rustdoc + examples + semver policy + cargo-deny for the published crates — CP-1 prerequisite) |
| Size budgets + distribution | m132 → m133 | CI size budgets (AppImage ≤30 MB, WebUI bundle ≤500 KB gzipped) + Docker multi-arch + .deb + signed AppImage |
Pre-scheduled 1.0 release removed — Avatarr's 1.0 tag happens organically if and when earned, not as a roadmap checkpoint. Shipped-milestone signals (m93 = Bazarr/Prowlarr work, m103 = WebUI ships, m104-m105 = Sonarr/Radarr import works) communicate readiness more precisely than a version number ceremony.
Distribution discipline — AppImage, Docker multi-arch, .deb, and signed releases all ship at m133 (deferred from m108 in the original roadmap to past the CRUD + QA + docs arc per the 2026-05-25 audit restructure). There is no per-commit AppImage rebuild during the m54-m132 development arc; cargo run / cargo test are the dev iteration loop.
Versioning: milestone N ships as v0.N.0; v0.N.1+ is strictly for post-ship bug fixes.
✨ Features (today)
- 🔍 Multi-source torrent search: TPB, YTS, EZTV, RuTracker (fallback chain — RuTracker disabled by default, requires account)
- 🌟 Quality preference: Select "Any" to prefer 1080p while accepting all qualities, or choose a specific quality (720p, 1080p, 2160p) to only show results matching that quality
- 📊 Smart scoring: Ranks results by title match, year, quality, seeders, file size
- 📺 TV support: Season packs, episode fallback, auto-discovery (
S01+), season ranges (S01-S05), complete series packs - 📽️ UHD routing: 2160p/4K movies automatically routed to UHD Movies library
- ⬆️ Smart quality upgrade: Incoming files compared by resolution and size — upgrades lower quality, fills missing episodes, replaces zero-byte files
- 👀 Scheduler-driven automation: RSS polling, missing-content search, grabbing, download-client sync, and hardlink-first import run as scheduled tasks (
avatarr-cli scheduler run) - 🏷️ IMDB/TMDB metadata: Resolves missing years and canonical titles via API lookup (year-aware caching, acronym-aware matching)
- ❓ Bare title resolution: Titles without a year or season tag are automatically resolved via TMDB — movies and TV shows correctly identified and categorized
- 💾 Persistent move logs: every import/move recorded in the SQLite
MoveLogstable — survives restarts, queryable - 🎁 Extras collection: Extras/bonus folders in TV packs automatically moved to Show/Featurettes/
- 🎥 Movie extraction: Movie files found in TV packs processed as regular movies
- 🌐 Cross-platform: Linux, Windows, macOS
- ⌨️ Three surfaces: Full-featured CLI, Slint desktop GUI, and React WebUI
- 🚀 First-run setup: Auto-generates config template on first launch
- 📄 Batch mode: Process list files with movies, TV seasons, season ranges, and discover mode
RSS / Search ──> Score & Rank ──> Grab ──> Download Sync ──> Import ──> Plex
(indexers) (decision engine) (qBittorrent) (scheduler) (hardlink/rename)
📚 Documentation
| Guide | What it covers |
|---|---|
| Getting started | Build (desktop + headless), the three surfaces, first run, the two-process model |
| Migrating from Sonarr/Radarr | Importing your library — with an honest account of what carries over |
| API reference | The Sonarr/Radarr-v3-compatible REST API: spec, auth, all 37 surfaces, WebSocket events, Bazarr/Prowlarr status |
| Configuration reference | Every config.toml key with its default and whether current code actually reads it |
| Troubleshooting | Symptom-indexed reference: health checks, backoff, stuck states, the smoke test |
| CONTRIBUTING | Development setup, gates, PR flow |
📦 Install
Docker (server)
The single-container deployment: API + WebUI + scheduler in one process
(server --with-scheduler), state under one /data volume, healthcheck
on /ping. Multi-arch (amd64 + arm64).
git clone https://codeberg.org/alan090/avatarr.git && cd avatarr
docker compose up -d --build # WebUI at http://localhost:7878
docker-compose.yml parameterizes the media and downloads mounts
(AVATARR_MEDIA_ROOT, AVATARR_DOWNLOADS_ROOT — default to local
./media + ./downloads). A switch-test profile adds real
qBittorrent + Bazarr for full-stack evaluation. Images are not yet on a
public registry — registry publishing arrives with v1.0.
.deb (Debian/Ubuntu server)
Built by cargo deb -p avatarr-cli (or grab the release-artifacts CI
artifact): installs /usr/bin/avatarr-cli plus a hardened systemd unit
(DynamicUser, state in /var/lib/avatarr) running the same
single-process server. Not auto-enabled:
sudo dpkg -i avatarr_*.deb
sudo systemctl enable --now avatarr
The unit documents the ReadWritePaths drop-in you need to grant your
media library to the sandboxed service.
AppImage (desktop GUI)
scripts/build-appimage.sh produces
dist-appimage/avatarr-<version>-x86_64.AppImage (~13 MB) — see
packaging/appimage/README.md for the
build-host and run-host requirements. Signed public releases arrive
with v1.0.
From source
Full walkthrough: docs/user/getting-started.md.
Prerequisites:
- Rust toolchain —
rust-toolchain.tomlpins the version; rustup picks it up automatically - Node.js 18+ — required even for CLI-only builds: the API crate embeds the WebUI bundle at compile time
- A C compiler (gcc/clang/MSVC) — required by some dependencies
- Desktop GUI only: fontconfig development headers
🐧 Linux
# Debian/Ubuntu
sudo apt install build-essential pkg-config libssl-dev nodejs npm
sudo apt install libfontconfig1-dev # only if building the GUI
# Arch/CachyOS
sudo pacman -S base-devel openssl nodejs npm
sudo pacman -S fontconfig # only if building the GUI
git clone https://codeberg.org/alan090/avatarr.git
cd avatarr
(cd crates/webui && npm ci && npm run build) # REQUIRED before any cargo build
cargo build --release # or: -p avatarr-cli (headless, skips GUI deps)
# Binaries: target/release/avatarr (GUI), target/release/avatarr-cli (CLI + server)
🪟 Windows
# Requires Visual Studio Build Tools with "Desktop development with C++"
# Install Rust: https://rustup.rs/ (use default MSVC toolchain)
# Install Node.js 18+: https://nodejs.org/
git clone https://codeberg.org/alan090/avatarr.git
cd avatarr
cd crates\webui; npm ci; npm run build; cd ..\.. # REQUIRED before any cargo build
cargo build --release
# Binaries: target\release\avatarr.exe (GUI), target\release\avatarr-cli.exe (CLI + server)
🍎 macOS
xcode-select --install
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
brew install node
git clone https://codeberg.org/alan090/avatarr.git
cd avatarr
(cd crates/webui && npm ci && npm run build) # REQUIRED before any cargo build
cargo build --release
# Binaries: target/release/avatarr (GUI), target/release/avatarr-cli (CLI + server)
Note: macOS builds are unsigned. Right-click and choose Open to bypass Gatekeeper on first launch.
Migrating from plex-pipeline-rs
If you previously ran plex-pipeline-rs, the v0.40.0 binary auto-migrates your ~/.config/plex-pipeline/ directory to ~/.config/avatarr/ on first launch. No manual action required. The migration is idempotent (it only fires when the new directory does not yet exist) and falls back to a recursive copy on EXDEV if your config and home directories live on different filesystems. See the v0.40.0 changelog entry for the full migration notes, including how to roll back.
🕹️ Usage
Two binaries ship from this workspace: avatarr is the desktop GUI;
avatarr-cli carries every command below, plus avatarr-cli server
(REST API + WebUI on :7878) and avatarr-cli scheduler run (the automation
daemon — RSS polling, searching, grabbing, importing). Nothing automatic
happens until the scheduler daemon is running — see the
getting started guide for the full picture.
# Search
avatarr-cli search "Big Adventure 1985" --type movie # Single movie
avatarr-cli search "Sunset Valley S01" --type tv # TV season
avatarr-cli search "Echo Company S05E14" --type tv # Single episode
avatarr-cli search --file list.txt --auto # Batch from file
avatarr-cli search --file list.txt --auto --dry-run # Preview only
avatarr-cli search "Night Runner 2003" --type movie --quality 1080p
# Daemons
avatarr-cli server # REST API v3 + WebUI (default port 7878)
avatarr-cli scheduler run # Automation daemon (RSS, search, grab, import)
avatarr-cli scheduler list # Show every scheduled task + cadence
# Movie library
avatarr-cli movie search "The Matrix" --year 1999 # Search TMDB
avatarr-cli movie add --tmdb-id 603 --path /media/Movies/Matrix --monitored
avatarr-cli movie add --tmdb-id 603 --path /media/Movies/Matrix --minimum-availability in-cinemas
avatarr-cli movie list # List all movies with monitor flag
avatarr-cli movie show --tmdb-id 603 # Show one movie row
avatarr-cli movie monitor --tmdb-id 603 # Mark monitored
avatarr-cli movie unmonitor --tmdb-id 603 # Mark unmonitored
# Series monitor state machine
avatarr-cli series list # List all series
avatarr-cli series show --tvdb-id 12345 # Show series + per-season subtable
avatarr-cli series monitor --tvdb-id 12345 # Toggle master Monitored on
avatarr-cli series apply-monitor --tvdb-id 12345 --strategy pilot # Cascade strategy through seasons + episodes
avatarr-cli series set-monitor-new-items --tvdb-id 12345 --mode all # Auto-monitor newly imported items
# Season + episode monitor toggles
avatarr-cli season monitor --tvdb-id 12345 --season 1
avatarr-cli episode monitor --tvdb-id 12345 --season 1 --range 1,3,5,7-9
avatarr-cli episode unmonitor --tvdb-id 12345 --season 1 --range 1-3,7,10-12 --dry-run
# Quality profiles
avatarr-cli quality list # List quality definitions
avatarr-cli profile show Any # Show profile detail + items
avatarr-cli profile create --clone Any --name "HD Only" # Clone a profile
avatarr-cli profile set "HD Only" --cutoff Bluray-1080p # Edit profile settings
avatarr-cli profile items add "HD Only" "HDTV-2160p" # Add quality to profile
# Migration from Sonarr/Radarr (see docs/user/migration.md first!)
avatarr-cli import sonarr /path/to/sonarr.db --dry-run
avatarr-cli import radarr /path/to/radarr.db --dry-run
# Diagnostics
avatarr-cli health # Run all five health checks now
avatarr-cli smoke # Hermetic end-to-end pipeline self-test
avatarr-cli --test-parse # Test torrent name parsers
Every command group has more — avatarr-cli --help is the full surface
(queue, pending, indexers, download clients, recycle bin, notifications,
OpenAPI spec, …).
📄 Input file format
# Movies — title with year
Big Adventure (1985)
Night Runner 2003
# TV — show name with S## tag
Sunset Valley S01
Echo Company S01-S03
The Drift S01+
# TV — individual episode
Sunset Valley S05E14
# TV — complete series pack (no season specified)
The Drift
# Bare title — resolved via TMDB (movie or TV detected automatically)
Big Adventure
⚙️ Configuration
Config file: ~/.config/avatarr/config.toml (auto-generated on first use).
The sample below shows the most-used keys — newer sections ([api] key/port,
[gui], recycle-bin retention, [tvdb]) are present in the generated file
with their defaults. The configuration reference
documents every key, its default, and whether current code actually reads it.
[qbittorrent]
url = "http://localhost:8080"
username = ""
password = ""
[paths]
staging_dir = "/tmp/staging"
movie_dir = "/media/Movies"
uhd_movie_dir = "/media/UHD Movies"
tv_dir = "/media/TV Shows"
[sources.tpb]
enabled = true
api_base = "https://apibay.org"
request_delay_ms = 2000
min_seeders = 1
[sources.yts]
enabled = true
[sources.eztv]
enabled = true
[sources.rutracker]
enabled = false
[tmdb]
api_key = ""
🏷️ Metadata resolution
When a torrent name lacks the year (common for TV shows), the monitor resolves the canonical title and year automatically. This ensures Plex-standard folder naming:
Movie Name (Year)/Movie Name (Year).ext
Show Name (Year)/Season N/Show Name (Year) - S01E01.ext
Resolution chain (first match wins):
- 🎞️ IMDB Suggestion API — free, no key needed
- 🎥 TMDB Search API — year-aware filtering (
primary_release_yearfor movies,first_air_date_yearfor TV); addapi_keyunder[tmdb]in config - 📝 Torrent name — if the year is already present
- 📁 Existing library folder — parsed name matched against disk, with automatic folder rename when the resolver provides a corrected canonical title
Cache keys include title, media type, and year — same-title entries from different years are resolved independently.
💾 Database
Avatarr uses SQLite via sqlx. The database file lives at:
- Linux:
~/.local/share/avatarr/avatarr.db - macOS:
~/Library/Application Support/avatarr/avatarr.db - Windows:
%LOCALAPPDATA%\avatarr\avatarr.db(non-roaming — avoids profile-sync thrash on growing SQLite files)
Override with the AVATARR_DATA_DIR environment variable for isolated dev/test installs:
AVATARR_DATA_DIR=/tmp/avatarr-dev avatarr
Schema lives in crates/core/migrations/*.sql. The pool opens with PRAGMA foreign_keys=ON, WAL journal mode, and busy_timeout=5s; migrations run automatically at CLI/GUI startup with exponential-backoff retry on transient busy errors. Failure to open is fatal with a per-variant error message (stderr for CLI; stderr + ~/.cache/avatarr/startup-error.log for GUI) and exit code 2.
Regenerating sqlx offline query cache (contributors)
After modifying a sqlx::query! macro:
cargo install sqlx-cli --no-default-features --features native-tls,sqlite
export DATABASE_URL="sqlite:///tmp/avatarr-prepare.db"
cargo sqlx database create
cargo sqlx migrate run --source crates/core/migrations
cargo sqlx prepare --workspace
Commit the resulting .sqlx/query-*.json files. CI runs with SQLX_OFFLINE=true — no DATABASE_URL required at build time.
🔧 Troubleshooting
Full symptom-indexed reference: docs/user/troubleshooting.md
(build failures, startup exit codes, WebUI auth, backoff ladders, stuck
downloads/imports, the five health checks, and the avatarr-cli smoke
self-test). The three most common:
| Issue | Solution |
|---|---|
Fresh-clone build fails referencing ../webui/dist/ |
Build the WebUI bundle first: (cd crates/webui && npm ci && npm run build) |
| qBittorrent connection failed | Check URL in Settings (default: http://localhost:8080), ensure Web UI is enabled |
| Nothing downloads automatically | The automation daemon isn't running — start avatarr-cli scheduler run alongside avatarr-cli server |
🏗️ Architecture
Cargo workspace with six crates:
| Crate | Purpose |
|---|---|
avatarr-core |
Library: domain models, repository layer, decision engine, scheduler, import pipeline, metadata, organizer, config |
avatarr-cli |
CLI binary (avatarr) via clap |
avatarr-parser |
Sonarr-ported parser library (episode/quality/language/release-group) — published on crates.io at 0.1.0 |
avatarr-gui |
Slint desktop GUI |
avatarr-api |
Axum API v3 server — WebUI data layer + Bazarr/Prowlarr compatibility surface |
avatarr-media |
TMDB/IMDB metadata resolution + pure Plex naming formatters, extracted from avatarr-core at m110 — independently versioned 0.1.0, consumed by core via re-export |
Platform: the avatarr crate family
avatarr is componentizing into a published crate family consumed by its own app crates and by external tools (see the ROADMAP Componentization section — m110/m111/m131 + the CP-1 publish gate):
avatarr-parser ─┐ ┌─ avatarr-cli
(crates.io) ├──> avatarr-core ─────┼─ avatarr-gui
avatarr-media ──┘ (app library) └─ avatarr-api ──> React WebUI
(extracted m110) (embedded assets)
│
└──────> medialint (first external consumer — path-dep until the CP-1 crates.io publish)
The library crates (avatarr-parser, avatarr-media) are pure — no SQLite, no app config — so external consumers like medialint can lint a media library against the Plex naming standard without dragging in the full manager.
Tech stack today (v0.70.0): Rust 2021, Tokio async runtime, reqwest for HTTP, quick-xml for Newznab/Torznab parsing, regex for parsers, clap for CLI, TOML for config, sqlx 0.8 against SQLite for the catalog, recycle bin, and move log, tempfile for the m70 hermetic smoke and integration test fixtures, wiremock for API client integration tests. The schema (crates/core/migrations/, 9 migrations, 51 tables) is stable since m43; domain models, repository layer, TVDB/TMDB API clients, the per-entity monitor state machine, scheduler subsystem (9 registered tasks), RSS polling + missing-search jobs, download client abstraction (IDownloadClient trait), indexer abstraction (directory-module layout, IndexerApi trait, feature-gated mocks), tracked downloads DB + broadcast event channels, hardlink-based file placement, the queryable recycle bin, and the avatarr smoke end-to-end self-test are complete through m70. See the roadmap for the full migration plan.
🤝 Heritage and attribution
Avatarr will port code from Sonarr and Radarr starting in m44 (parser port phase 1). Both upstream projects are GPL-3.0; Avatarr is GPL-3.0-or-later, so the port is legally clean. Each ported file gets a header comment crediting the source commit, and THANKS.md is the canonical attribution list (currently a placeholder until m44 lands).
This project is also a Rust rewrite of the original Python plex-pipeline (archived). The Rust port is ~484× faster startup, ~30× faster parsing, ~5.6× less memory, and ~7.7× smaller binary than the PyQt6 original.
📜 Changelog
See CHANGELOG.md for the full release history. The most recent release is v0.70.0 (m70 indexer abstraction + avatarr smoke self-test).
⚖️ License
GPL-3.0-or-later — see LICENSE. This is required for the m44 parser port to be legally clean against Sonarr/Radarr's GPL-3.0; the license is locked and will not change.