Skip to content

Real-time Plex Watchlist synchronization triggered by Radarr/Sonarr deletions, with TMDB/TVDB/IMDB matching, fuzzy fallback, and cached Plex index.

License

Notifications You must be signed in to change notification settings

MrCee/plexpulse

Repository files navigation

🎬 PlexPulse

Keep your Plex Watchlist aligned with reality.

PlexPulse ensures your Plex Account Watchlist reflects what is actually present in your Plex libraries — safely, deterministically, and without guesswork.

No stale Watchlist entries.
No accidental removals.
No operator memory required.


✨ What PlexPulse Is

PlexPulse is a library-driven reconciliation system for Plex Watchlists.

It answers one authoritative question:

“Does Plex still report this item as present?”

Everything flows from that single truth.

PlexPulse observes Plex, records belief in a persistent ledger, and applies cleanup policy deliberately — never implicitly.


❓ What Problem PlexPulse Solves

Plex Watchlists can drift over time:

  • Media is deleted
  • Libraries are reorganised
  • Arr tools remove items
  • Plex metadata refreshes lag behind filesystem changes

PlexPulse prevents Watchlist drift by reacting only to what Plex itself reports — not filesystem state, not assumptions, and not partial signals.


🧭 Core Principles

  • Plex is authoritative
  • Nothing is inferred
  • State is persisted
  • Side-effects are explicit
  • All actions are auditable
  • Safe to re-run, safe to restart

🧠 Core Architecture

PlexPulse is a deterministic multi-phase system with a persistent state plane.

The Phase Model

PHASE A — Baseline   (Observe Plex)
PHASE B — Backfill   (Annotate Arr intent)
PHASE C — Ledger     (Persistent State Plane)
PHASE D — Reconcile  (Enforce policy)

Only Phase D performs side-effects.


The Ledger (Persistent State Plane)

PlexPulse maintains a persistent JSON-Lines ledger representing the system’s current belief about every known media item.

The ledger is:

  • Human-auditable
  • jq-friendly
  • Persistent across restarts
  • Never truncated automatically

It is not a cache, log, or event stream.


Separation of Observation, State, and Action

  • Baseline observes Plex and updates belief
  • Backfill records historical Arr intent
  • Ledger stores truth
  • Reconcile applies policy

No phase collapses responsibilities.


🧭 Architecture & System Flow

The diagram below shows how executable phases interact with the persistent ledger and where side-effects are allowed to occur.

flowchart TB
  %% PlexPulse — Phase Interaction Model

  classDef entry fill:#ECFEFF,stroke:#0891B2,stroke-width:2px,color:#000,font-weight:bold
  classDef phase fill:#FFF7CC,stroke:#D4B106,stroke-width:2px,color:#000,font-weight:bold
  classDef ledger fill:#E6F0FF,stroke:#3B82F6,stroke-width:3px,color:#000,font-weight:bold
  classDef exec fill:#FEE2E2,stroke:#DC2626,stroke-width:3px,color:#000,font-weight:bold

  subgraph SE["ENTRYPOINT CONTRACT<br/>(Startup Order)"]
    E1["1) Ensure state dir exists<br/>
        2) Ensure ledger exists<br/>
        3) Run Baseline (A)<br/>
        4) Run Backfill (B)<br/>
        5) Run Reconcile (D)<br/>
        6) Start server"]
  end
  class SE,E1 entry

  subgraph SA["PHASE A — BASELINE"]
    A1["Observe Plex library truth<br/>Mutates ledger only<br/>NO side-effects"]
  end
  class SA,A1 phase

  subgraph SB["PHASE B — BACKFILL"]
    B1["Radarr intent only"]
    B2["Sonarr intent only"]
  end
  class SB,B1,B2 phase

  subgraph SC["PHASE C — LEDGER"]
    C1["Persistent system belief<br/>Survives restarts"]
  end
  class SC,C1 ledger

  subgraph SD["PHASE D — RECONCILE"]
    D1["Apply policy<br/>Side-effects via Admin API"]
  end
  class SD,D1 phase

  subgraph SS["WEBHOOK SERVER"]
    S1["Executes Watchlist + Arr deletions"]
  end
  class SS,S1 exec

  A1 --> C1
  B1 --> C1
  B2 --> C1
  C1 --> D1
  D1 --> S1

  E1 -.-> A1
  E1 -.-> B1
  E1 -.-> D1
  E1 -.-> S1
Loading

🔁 Media Lifecycle Model

Every item follows a strict lifecycle:

current → missing → deleted
  • current: Plex reports the item
  • missing: Plex no longer reports it
  • deleted: Absence persisted beyond policy threshold

Transitions are driven by Plex observation and elapsed time only.


🚀 Runtime & Startup Behaviour

On container startup, PlexPulse always:

  1. Ensures the state directory exists
  2. Ensures the ledger file exists
  3. Runs Baseline
  4. Runs Backfill
  5. Runs Reconcile
  6. Starts the webhook server

No steps are skipped.


What Is Safe to Re-run

The following commands are safe and idempotent:

maintenance/plexpulse-baseline.sh
maintenance/plexpulse-reconcile.sh

🔔 Integrations

PlexPulse integrates with Plex, Radarr, and Sonarr as a coordinated system.

System Role in PlexPulse
Plex Authoritative source of library truth
Radarr Movie deletion intent + acceleration signal
Sonarr Series deletion intent + acceleration signal

Plex determines what is real.
Radarr and Sonarr provide intent evidence and early signals.


🎞 Plex (Authoritative Source)

  • Baseline scans query Plex libraries
  • Status is derived solely from Plex observation
  • Filesystem state is never inspected

If Plex still reports an item, it is not deleted, regardless of Arr activity.


🎬 Radarr & Sonarr (Intent + Acceleration)

Radarr and Sonarr are core integrations in PlexPulse.

They provide:

  • Deletion intent evidence (recorded in the ledger)
  • Immediate Watchlist cleanup signals when items are removed

Radarr and Sonarr never determine truth. Plex remains the authoritative source of library presence.

Webhook delivery is best-effort:

  • Missed or delayed webhooks are safe
  • Baseline + Reconcile will always converge state correctly

🔧 Integration Setup (Required)

Radarr and Sonarr must be configured correctly to ensure:

  • full-series / full-movie deletions behave as expected
  • Watchlist cleanup occurs only when Plex no longer reports the item

For step-by-step setup, including:

  • exact webhook configuration
  • required event selections
  • delete-file vs remove-only semantics
  • common misconfiguration pitfalls

👉 See the full integration setup guide:
docs/setup-integrations.md


Radarr Webhook Setup (Movies)

Configure in Radarr → Settings → Connect → Add → Webhook:

Field Value
URL http://<plexpulse-host>:8080/webhook/radarr
Method POST
On Delete ✅ Enabled

Radarr sends:

  • movie.tmdbIdtmdb://<tmdbId>

Sonarr Webhook Setup (Series)

Configure in Sonarr → Settings → Connect → Add → Webhook:

Field Value
URL http://<plexpulse-host>:8080/webhook/sonarr
Method POST
On Delete ✅ Enabled

Sonarr sends:

  • series.tvdbIdtvdb://<tvdbId>

Integration Semantics

  • Webhooks are best-effort
  • Missed webhooks are not a problem
  • Baseline + Reconcile always converge state correctly
  • Arr tools never determine status

🧩 Webhook Server

The webhook server is the only execution surface in PlexPulse.

It:

  • Maintains the in-memory Watchlist index (ACTIVE_INDEX)
  • Executes Watchlist removals
  • Executes Arr deletions
  • Exposes Admin and Inspection APIs

Shell scripts never call Plex or Arr APIs directly.


🌐 Webhook & API Endpoints

Arr → PlexPulse (Incoming)

Endpoint Source Purpose
POST /webhook/radarr Radarr Movie deletion intent + immediate cleanup
POST /webhook/sonarr Sonarr Series deletion intent + immediate cleanup

🔎 Inspection Endpoints (Read-Only)

Endpoint Purpose
GET /inspect/index Full Watchlist index
GET /inspect/watchlist Current Watchlist items
GET /inspect/lookup/<external_id> Resolve canonical ID

🛠 Admin Endpoints (Operator Controlled)

Endpoint Method Purpose
/admin/index-ready GET Check index readiness
/admin/rebuild-index POST Rebuild Watchlist index
/admin/reconcile/library-deletions POST Enforce Watchlist cleanup
/admin/reconcile/arr-deletions/preview GET Preview Arr deletions
/admin/reconcile/arr-deletions/execute POST Execute Arr deletions

🛠 Operator Commands

Command Purpose
maintenance/plexpulse-baseline.sh Observe Plex and update ledger
maintenance/plexpulse-reconcile.sh Enforce deletion policy

Commands are safe to run:

  • manually
  • via cron
  • after large library changes

🐳 Deployment Model

  • Docker-first
  • Single bind-mounted state directory
  • No Docker-in-Docker
  • No hidden host assumptions

🛡 Safety Guarantees

PlexPulse never:

  • Deletes media files
  • Modifies Plex libraries
  • Performs silent automation
  • Acts on partial data

All actions are explicit, gated, and auditable.


📁 State & Persistence

All runtime state lives in a single directory:

${PLEXPULSE_STATE_DIR}/${PLEXPULSE_LIBRARY_LEDGER}

⚙️ Configuration

Configuration is provided via .env.

Required and optional values are documented in .env.example.


📜 Versioning & Compatibility Notes

PlexPulse uses internal versioning for compatibility and evolution.

These versions are maintainer-facing and do not affect day-to-day usage.


📄 License

MIT License — see LICENSE.


PlexPulse
Because your Watchlist should reflect reality, not memory.

About

Real-time Plex Watchlist synchronization triggered by Radarr/Sonarr deletions, with TMDB/TVDB/IMDB matching, fuzzy fallback, and cached Plex index.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published