tautulli-remap

command module
v1.4.25 Latest Latest
Warning

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

Go to latest
Published: Jul 15, 2026 License: GPL-2.0, GPL-3.0 Imports: 14 Imported by: 0

README

tautulli-remap

Image Size Platforms Test coverage Mutation OpenSSF Best Practices OpenSSF Scorecard SBOM

Fix broken Tautulli watch history after reorganizing your Plex libraries.

What it does

When you reorganize your Plex libraries (move files, re-add content, change folder structure), Plex assigns new internal IDs to your media. This breaks Tautulli's watch history — it can no longer link history entries to the right items. This tool automatically finds the correct new IDs and updates Tautulli's database, preserving your watch history and statistics.

For each stale entry, it finds the correct current rating key in Plex using a chain of strategies, most precise first:

  1. Episode-GUID resolution (TV shows) — resolves a show through one of its watched episodes' stable Plex GUIDs, which map directly to the show's current key. Exact and collision-free, and it restores the show's full watch history (all seasons and episodes).
  2. GUID match — Plex's globally unique identifier; covers movies and shows whose history still carries a show-level GUID (e.g. the legacy thetvdb agent).
  3. Title+year match (fallback) — matches by title and release year when no GUID resolves.
  4. Title-only with media type guard (optional) — last resort matching by title alone, restricted to the same media type to reduce false positives.
Why this design
  • Three run modesSCHEDULE_INTERVAL (e.g. "24h") for a built-in timer, SCHEDULE_INTERVAL=off for resident-idle (stays healthy, awaits docker exec ... tautulli-remap trigger), or tautulli-remap trigger for a one-shot pass that exits 0/1.
  • Dry-run by default for safety — no changes are applied until you explicitly set DRY_RUN=false, so you can always preview first.
  • Matching strategies with increasing aggressiveness — starts with the exact ones (episode-GUID resolution for shows, GUID match for movies), falls back to title+year, and optionally title-only, giving you control over the risk/coverage tradeoff.
  • Stdlib-first, minimal dependencies — pure Go on the standard library plus a small first-party shared-lib set (health, httpx) and golang.org/x/sync, minimizing supply-chain risk.
  • Distroless and rootless — runs as nonroot on gcr.io/distroless/static with no shell or package manager.

Quick start

Images are published to both ghcr.io/cplieger/tautulli-remap and docker.io/cplieger/tautulli-remap — use whichever you prefer.

services:
  tautulli-remap:
    image: ghcr.io/cplieger/tautulli-remap:latest
    container_name: tautulli-remap
    restart: unless-stopped
    user: "1000:1000"  # match your host user

    environment:
      TAUTULLI_URL: "http://tautulli:8181"
      TAUTULLI_APIKEY: "your-tautulli-apikey"
      PLEX_URL: "http://plex:32400"
      PLEX_TOKEN: "your-plex-token"
      SCHEDULE_INTERVAL: "24h"  # Go duration; "off" = resident-idle
      FALLBACK_TITLE_YEAR: "true"
      FALLBACK_TITLE_ONLY: "false"  # risk of false matches
      DRY_RUN: "true"  # set to false to apply changes

Configuration reference

Variable Description Default Required
TAUTULLI_URL Tautulli instance URL (Docker DNS name or LAN IP) http://tautulli:8181 No
TAUTULLI_APIKEY Tautulli API key (Settings → Web Interface → API Key) - Yes
PLEX_URL Plex Media Server URL (Docker DNS name or LAN IP) http://plex:32400 No
PLEX_TOKEN Plex authentication token (see Plex support article) - Yes
SCHEDULE_INTERVAL Go duration between remap runs (e.g. 24h, 6h30m). off/disabled/0 = resident-idle (awaits external trigger via tautulli-remap trigger) off No
FALLBACK_TITLE_YEAR Try title+year matching when GUID match fails true No
FALLBACK_TITLE_ONLY Try title-only matching as last resort (risk of false matches) false No
DRY_RUN Log what would change without applying — set to false to apply true No

Subcommands

Subcommand Description
tautulli-remap health Checks the /tmp/.healthy marker file. Used as the Docker HEALTHCHECK. Exits 0 (healthy) or 1 (unhealthy).
tautulli-remap trigger Executes a single remap pass immediately. Exits 0 on success, 1 on failure. Designed for docker exec or Ofelia job-exec.

Use SCHEDULE_INTERVAL=off (resident-idle, one of the three run modes) with an external scheduler like Ofelia:

services:
  tautulli-remap:
    image: ghcr.io/cplieger/tautulli-remap:latest
    environment:
      SCHEDULE_INTERVAL: "off"  # resident-idle, awaits trigger
      DRY_RUN: "false"
      # ... other env vars
    labels:
      ofelia.enabled: "true"
      ofelia.job-exec.tautulli-remap.schedule: "0 0 3 * * *"
      ofelia.job-exec.tautulli-remap.command: "/tautulli-remap trigger"

This keeps the container healthy (passing healthchecks) while delegating scheduling to Ofelia.

Healthcheck

The container includes a built-in Docker healthcheck via the /tautulli-remap health subcommand, which checks for a marker file at /tmp/.healthy. What that marker reflects depends on the run mode:

  • Scheduled mode (SCHEDULE_INTERVAL set to a duration): the main process refreshes /tmp/.healthy after each run and marks the container unhealthy after 3 consecutive failed runs (Tautulli or Plex APIs unreachable, returning errors, or the remap logic failing), recovering automatically on the next successful run (including runs where nothing needs remapping).
  • Resident-idle mode (SCHEDULE_INTERVAL=off): the marker reflects the resident process's liveness. Each tautulli-remap trigger run reports its own outcome via its exit code (0 success / 1 failure) for the external scheduler to act on; a failed trigger deliberately does not mark the long-lived container unhealthy.

Security

No vulnerabilities found. All scans clean across all scanners.

Tool Result
govulncheck No vulnerabilities in call graph
golangci-lint (gosec, gocritic) 0 issues
trivy 0 vulnerabilities
grype 0 vulnerabilities
gitleaks No secrets detected
semgrep 1 info (false positive)
hadolint Clean

No network listener; connects outbound to Tautulli and Plex only. Set DRY_RUN=true on first run to preview changes safely. API tokens are never logged. Stdlib-first, with a minimal first-party dependency set. Runs as nonroot on a distroless base image with no shell, under the hardened compose profile (read_only: true, cap_drop: [ALL], no-new-privileges:true, 16 MB tmpfs for /tmp).

Details for advanced users: All HTTP clients use explicit timeouts (2 min client, 30s per direct request, 60s for the Plex library fetch). Transient failures on Tautulli and Plex reads are retried with bounded backoff (each attempt within the 2-min client timeout); mutating Tautulli calls are never retried. Response bodies capped via io.LimitReader (30 MB Tautulli, 40 MB Plex library / 10 MB sections). Rating keys validated as numeric before URL interpolation (prevents path traversal). Plex token sent via X-Plex-Token header, not query string. HTTP error messages sanitized to strip query parameters (prevents API key leakage in logs). No unsafe, reflect, os/exec, or file I/O beyond the health marker.

Dependencies

All dependencies are updated automatically via Renovate and pinned by digest or version for reproducibility.

Dependency Source
golang Go
gcr.io/distroless/static Distroless

Credits

This is an original tool that builds upon Tautulli. Inspired by SwiftPanda16's Tautulli rating key update script.

Contributing

Issues and pull requests are welcome. Please open an issue first for larger changes so the approach can be discussed before implementation.

Disclaimer

This project is built with care and follows security best practices, but it is intended for personal / self-hosted use. No guarantees of fitness for production environments. Use at your own risk.

This project was built with AI-assisted tooling using Claude Opus and Kiro. The human maintainer defines architecture, supervises implementation, and makes all final decisions.

License

This project is licensed under the GNU General Public License v3.0.

Documentation

Overview

Package main is the entry point for tautulli-remap, a tool that repairs Tautulli watch history after Plex library reorganizations by finding and updating stale rating keys.

Directories

Path Synopsis
internal
config
Package config loads and validates the application configuration from environment variables.
Package config loads and validates the application configuration from environment variables.
orchestrator
Package orchestrator coordinates the tautulli-remap workflow, driving history collection, stale-key detection, Plex library indexing, matching, and metadata updates across the Tautulli and Plex APIs.
Package orchestrator coordinates the tautulli-remap workflow, driving history collection, stale-key detection, Plex library indexing, matching, and metadata updates across the Tautulli and Plex APIs.
plex
Package plex adapts the shared github.com/cplieger/plexapi client to the remap workflow's types.
Package plex adapts the shared github.com/cplieger/plexapi client to the remap workflow's types.
remap
Package remap implements the matching and indexing logic that maps stale Tautulli rating keys to current Plex rating keys after library reorganizations.
Package remap implements the matching and indexing logic that maps stale Tautulli rating keys to current Plex rating keys after library reorganizations.
tautulli
Package tautulli provides a client for the Tautulli API v2.
Package tautulli provides a client for the Tautulli API v2.

Jump to

Keyboard shortcuts

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