Skip to content

LAP87/Symlinkarr

Repository files navigation

Symlinkarr

Rust Docker SQLite Plex Emby Jellyfin

Symlinkarr turns Real-Debrid-backed media into a clean local library.

It scans your source mount, matches files to ID-tagged movie and series folders, writes stable symlinks, and keeps track of them in SQLite. It works with plain folders or alongside Plex, Emby, and Jellyfin.

If your current stack looks like "RD mount + Sonarr/Radarr + a messy library full of stale or misplaced links", Symlinkarr is the cleanup and repair layer between the mount and your media server.

Screenshots

CLI usage Web UI
Symlinkarr CLI usage Symlinkarr Web UI setup screen

What It Does

  • scans RD-backed mounts and local library folders
  • matches against {tvdb-*} and {tmdb-*} tagged folders
  • creates and updates symlinks in a repeatable way
  • repairs dead links and finds missing content
  • audits bad, stale, or misplaced links before cleanup
  • can refresh Plex, Emby, and Jellyfin after link changes
  • avoids hammering your media server after large cleanup or repair runs

No media server is required.

Integrations

  • Real-Debrid-backed mounts such as Zurg and Decypharr
  • Sonarr and Radarr
  • Prowlarr
  • Bazarr
  • Tautulli
  • TMDB and TVDB
  • Debrid Media Manager
  • Plex, Emby, and Jellyfin, optionally and together

How To Run It

README examples use symlinkarr ... as the normal command form.

  • release binary install: run ./symlinkarr ... or symlinkarr ... if it is on your PATH
  • source checkout: run cargo run -- ...
  • Docker: mainly for long-running daemon mode via docker compose up -d

Example:

symlinkarr scan --dry-run
cargo run -- scan --dry-run

Quick Start

Install

Download a release tarball from GitHub Releases, or build locally with Cargo, or run with Docker.

Release binary example:

tar -xzf symlinkarr-<version>-linux-amd64.tar.gz
cd symlinkarr-<version>-linux-amd64
./symlinkarr --help

Configure

Start from config.example.yaml.

Minimum:

  • one or more library paths
  • one or more source paths
  • a writable SQLite db_path
  • TMDB and TVDB credentials if you want full metadata matching

For a fresh local install, run symlinkarr bootstrap to create a starter config and directories. Docker users can skip this — compose already mounts config and data. To restore from an existing backup into a fresh install: symlinkarr restore <path-to-backup.json>. Works without a config file — useful for both local and Docker fresh installs (e.g. docker exec symlinkarr symlinkarr restore /app/backups/<file>.json).

From source:

cargo run -- config validate --output json
cargo run -- doctor --output json

From a release binary:

./symlinkarr config validate --output json
./symlinkarr doctor --output json

Run

From source:

cargo run -- scan --dry-run
cargo run -- scan
cargo run -- web

From a release binary:

./symlinkarr scan --dry-run
./symlinkarr scan
./symlinkarr web

Web UI default: http://127.0.0.1:8726

Local Docker Daemon

For a host-local daemon install, keep Symlinkarr in its own compose file and mount the real host paths directly.

Example local stack:

services:
  symlinkarr:
    image: ghcr.io/lap87/symlinkarr:rc
    container_name: symlinkarr
    restart: unless-stopped
    user: "1000:1000"
    command: ["--config", "/app/config/config.yaml", "daemon"]
    extra_hosts:
      - "host.docker.internal:host-gateway"
    volumes:
      - ./config.docker.yaml:/app/config/config.yaml:ro
      - ./data:/app/data
      - /mnt:/mnt:rslave
      - /path/to/secrets:/app/secrets:ro

Typical local operations:

docker compose -f docker-compose.local.yml up -d
docker compose -f docker-compose.local.yml logs -f symlinkarr
docker exec symlinkarr symlinkarr --config /app/config/config.yaml status --output json
docker stop symlinkarr

The floating rc tag follows the newest release candidate. Stable installs should use latest; exact-version tags remain available when a fixed deployment is preferred.

If you are replacing another standalone tool, stop that container separately instead of editing its compose file unless both services truly belong to the same stack.

Security Modes

Symlinkarr is meant to run like most home *arr apps: local by default, locked down if you expose it.

  • local-only: bind to 127.0.0.1, keep allow_remote: false, and use the UI/API from the same host or through your own tunnel/reverse proxy.
  • remote UI: bind beyond loopback, set allow_remote: true, and configure web.username + web.password.
  • scripts/API: optionally add web.api_key for scripts that should use Authorization: Bearer ... or X-API-Key.

Practical rules:

  • local-only is trusted mode: no built-in auth is required
  • web.username + web.password protect the HTML UI and API with HTTP Basic auth
  • web.api_key protects JSON API clients via Authorization: Bearer ... or X-API-Key
  • when the UI is exposed remotely, browser forms use a session cookie plus CSRF token
  • remote UI access requires web.username + web.password; an API key alone is not enough for the built-in HTML UI

Recommended default:

  • keep the built-in UI loopback-only unless you have a specific reason to expose it

Metadata Cache Policy

Symlinkarr keeps TMDB/TVDB metadata cached for a long time on purpose.

  • metadata is mostly stable, while API timeouts and rate limits are expensive
  • if one title looks wrong, refresh that cache entry instead of clearing everything
  • short global TTLs mostly create extra API calls

Useful commands:

  • symlinkarr cache invalidate tmdb:12345
  • symlinkarr cache invalidate tmdb:tv:
  • symlinkarr cache invalidate anime-lists
  • symlinkarr cache clear

Backup Policy

Symlinkarr backups keep the important app state together:

  • a JSON manifest of active symlinks for normal restore flows
  • a sibling SQLite snapshot from backup create, so you can recover the database too
  • app-state snapshots for the active config.yaml and any secretfile:-backed secrets the current install can see

Use the backup names deliberately:

  • Symlinkarr Backup: the main backup to keep for day-to-day recovery
  • Restore Point: a lighter rollback point created around repair, cleanup, or daemon work

This is closer to a real app backup now, but not identical to Sonarr/Radarr yet:

  • restoring a Symlinkarr Backup from an already configured install can write link state, SQLite state, config, and secretfile: secrets back to the current install
  • standalone/no-config restore only recreates secrets inside the config tree or the standard Docker /app/secrets layout; other external secret paths still need to be recreated manually
  • secrets that only exist as environment variables still live outside the backup set
  • a truly fresh install still needs its config/secrets placed before first startup

Current-format backups are checked during backup list and backup restore, so corrupted or tampered backups fail loudly instead of half-restoring. Restore also stays confined to the configured backup.path, so a mistyped absolute path or symlink escape cannot make the restore flow read arbitrary files outside the backup directory. When old scheduled backups rotate out, their paired .sqlite3 snapshots and app-state bundles are removed with them so retention limits still bound disk usage.

Common Commands

symlinkarr scan --dry-run
symlinkarr scan --library Anime --search-missing
symlinkarr backfill --arr sonarr-anime --dry-run
symlinkarr backfill --arr all --search-missing
symlinkarr backfill --arr sonarr-anime --item 86-eighty-six --dry-run --search-missing
symlinkarr status --health
symlinkarr status --health --output json
symlinkarr cleanup audit --scope anime
symlinkarr cleanup prune --report <REPORT.json>
symlinkarr repair auto --dry-run
symlinkarr discover list
symlinkarr import --source /mnt/rd --movie-destination /library/movies --tv-destination /library/tv --anime-destination /library/anime --content-type auto --mode preview
symlinkarr cache status
symlinkarr cache invalidate tmdb:12345
symlinkarr cache invalidate tmdb:tv:
symlinkarr web

If you are running from a source checkout, prepend cargo run -- to the same commands.

Advanced note: cleanup remediate-anime is for older anime libraries with mixed roots or Plex Hama AniDB/TVDB duplicates. Most setups do not need it. Discover note: discover list previews where source files would land for tagged folders that still look empty or underlinked. Web/API discover is read-only for now. Import note: import is for bootstrap/recovery when you already have RD/DMM/provider content but not the matching local library folders or Symlinkarr DB. See Provider Source Import.

Why Use It

  • keep a Real-Debrid-backed library usable without hand-sorting files
  • keep Sonarr/Radarr-style ID-tagged folders clean
  • detect and repair bad symlinks before Plex, Emby, or Jellyfin drift too far
  • clean up legacy linked libraries

Known Limits

  • anime specials without usable anime-lists numbering hints may still need manual search terms, because many indexers are weak at S00Exx-style anime queries

Docs

About

Intelligent symlink manager for Real-Debrid media libraries

Resources

Stars

Watchers

Forks

Releases

Packages

Used by

Contributors

Languages