Skip to content

Modules

chodeus edited this page Apr 20, 2026 · 26 revisions

Modules

CHUB ships twelve modules. Each one is a scheduled chore you can also run on demand. Every module has its own section in config.yml and its own page under Settings → Modules in the UI.

Jump to a module:


What every module supports

  • Dry run — when dry_run: true, the module logs what it would do without making changes. Turn this on the first time you try a module.
  • Log leveldebug / info / warning / error, per module. Default is info. Flip to debug while you're diagnosing a problem, then back.
  • Cancel from the UI — Settings → Jobs → click the running job → Cancel. Eleven of the twelve modules stop cleanly on the next iteration (see each module below). border_replacerr is the exception — it runs to completion today; restart the container if you truly need to interrupt it.
  • Run history — visible in Settings → Jobs with full log output.

🖼️ poster_renamerr

What it does. Walks your Kometa (or other) asset folders, matches each image against your Radarr/Sonarr/Plex libraries, renames the files to match, and copies/moves/hardlinks them into your destination tree. Can optionally chain into border_replacerr and poster_cleanarr as a post-hook.

Cancellable: yes.

Gotcha: if nothing seems to be moving, check that dry_run is off, that destination_dir is writable by your PUID/PGID, and that action_type: hardlink isn't crossing filesystems.

See Kometa Integration for the end-to-end setup.

poster_renamerr:
  dry_run: false
  log_level: info
  action_type: copy                     # copy | move | hardlink
  asset_folders: true                   # expect Kometa-style per-item folders
  sync_posters: false
  print_only_renames: false
  run_border_replacerr: false           # chain border_replacerr after rename
  incremental_border_replacerr: false   # only border newly renamed posters
  run_cleanarr: false                   # chain poster_cleanarr after rename
  report_unmatched_assets: false        # chain unmatched_assets report
  source_dirs:
    - /kometa
  destination_dir: /posters
  instances:
    - radarr_main
    - sonarr_main
    - plex_main:
        library_names: ["Movies", "TV Shows"]
        add_posters: true               # push straight to Plex via API

🎨 border_replacerr

What it does. Re-applies a brand or holiday border to every poster in your tree. Strips any existing border and paints a new one of border_width pixels using a random color from border_colors — or a holiday palette if today falls within a holiday window.

Cancellable: not yet. If you start a big run and need to stop it, you'll need to restart the container.

Gotcha: if two holidays overlap, whichever is listed first wins.

border_replacerr:
  dry_run: false
  log_level: info
  source_dirs:
    - /posters
  destination_dir: /posters
  border_width: 26
  skip: false                           # skip all border work this run
  border_colors:
    - "#ff7300"
  ignore_folders: []                    # folders under source_dirs to skip
  exclusion_list: null                  # individual filenames to leave alone
  holidays:
    - name: halloween
      schedule: "10-01:10-31"           # MM-DD:MM-DD, year-agnostic
      colors: ["#FF6600", "#000000"]
    - name: christmas
      schedule: "12-01:12-26"
      colors: ["#C8102E", "#00843D"]

🧹 poster_cleanarr

What it does. Removes stale / orphaned poster metadata from Plex's internal folder — optionally empties the trash, cleans bundles, and optimizes the Plex database.

Cancellable: yes.

Gotcha: plex_path must be a filesystem path (e.g. /plex-config/Library/Application Support/Plex Media Server/Metadata), not a URL.

Valid modes: report (dry run — lists orphaned images), move (relocates to a Poster Cleanarr Restore folder), remove (deletes), restore (moves restore-folder items back), clear (deletes the restore folder), nothing (skips image work but still runs housekeeping if enabled). Start with report, then move, then remove.

poster_cleanarr:
  log_level: info
  mode: report                          # report | move | remove | restore | clear | nothing
  plex_path: "/plex-config/Library/Application Support/Plex Media Server/Metadata"
  photo_transcoder: false               # also clean PhotoTranscoder cache
  empty_trash: false                    # trigger Plex's Empty Trash
  clean_bundles: false                  # trigger Plex's Clean Bundles
  optimize_db: false                    # trigger Plex's Optimize DB
  ignore_running: false                 # skip when Plex is active
  sleep: 60                             # seconds between Plex API calls
  timeout: 600                          # seconds to wait for Plex tasks
  instances:
    - plex_main

🏷️ labelarr

What it does. Mirrors tags in Radarr/Sonarr into Plex labels. If you tag an item favorite in Sonarr, it shows up with the favorite label in the Plex library you've mapped.

Cancellable: yes.

Gotcha: label updates are applied in batch — if you untag a large number of items in the ARR, expect the corresponding Plex labels to update on the next run, not instantly.

labelarr:
  dry_run: false
  log_level: info
  mappings:
    - app_instance: sonarr_main
      labels: [watched, favorite]
      plex_instances:
        - instance: plex_main
          library_names: ["TV Shows"]
    - app_instance: radarr_main
      labels: [favorite]
      plex_instances:
        - instance: plex_main
          library_names: ["Movies"]

🔍 jduparr

What it does. Finds duplicate files across your media tree by content hash. Persists hashes to a database so repeat runs are incremental instead of re-hashing everything.

Cancellable: yes.

Gotcha: the first run on a large library takes hours. Subsequent runs are fast because only new/changed files are rehashed. hash_database can't contain null bytes or start with - (a safety check — see Troubleshooting if you hit it).

jduparr:
  dry_run: false
  log_level: info
  hash_database: /config/jduparr.db
  source_dirs:
    - /media/movies
    - /media/tv

🔗 nohl

What it does. Finds media files that aren't hardlinked to your downloader's completed directory, which typically means a broken rename or a file that was re-imported without a hardlink. Optionally re-queues an upgrade search in the ARR to fix them.

Cancellable: yes.

nohl:
  dry_run: false
  log_level: info
  searches: 10                          # how many re-searches to queue per run
  print_files: false                    # log the full list of non-hardlinked files
  source_dirs:
    - path: /media/movies
      mode: movie                       # movie | series
    - path: /media/tv
      mode: series
  exclude_profiles: []                  # ARR quality-profile names to skip
  exclude_movies: []                    # movie titles to skip
  exclude_series: []                    # series titles to skip
  instances:
    - radarr_main
    - sonarr_main

unmatched_assets

What it does. Reports media items that don't have a matching poster in your renamed tree. Runs standalone or as a post-hook on poster_renamerr (set report_unmatched_assets: true on poster_renamerr to chain them).

Cancellable: yes.

unmatched_assets:
  dry_run: false
  log_level: info
  ignore_folders: []                    # folders to skip while scanning posters
  ignore_profiles: []                   # ARR quality profiles to ignore
  ignore_titles: []                     # media titles to ignore
  ignore_tags: []                       # ARR tags to ignore
  ignore_collections: []                # Plex collection names to ignore
  ignore_unmonitored: false             # skip unmonitored items entirely
  instances:
    - radarr_main
    - sonarr_main
    - plex_main

⬆️ upgradinatorr

What it does. Picks a fixed number of items per ARR instance that haven't been searched recently, and fires an upgrade search on them. Tags items after searching so it doesn't pick the same ones again right away. Lidarr is fully supported — album search, artist grouping, and all three search modes.

Cancellable: yes.

upgradinatorr:
  dry_run: false
  log_level: info
  instances_list:
    - instance: radarr_main
      count: 10                         # items to search per run
      tag_name: chub-upgradinatorr      # tag applied after search
      ignore_tag: ignore                # skip items carrying this tag
      unattended: false
      search_mode: upgrade              # upgrade | missing | cutoff
    - instance: sonarr_main
      count: 5
      tag_name: chub-upgradinatorr
      ignore_tag: ignore
      season_monitored_threshold: 0.5   # Sonarr: require ≥ this fraction of monitored seasons
      search_mode: upgrade

✏️ renameinatorr

What it does. Walks Radarr/Sonarr and applies the ARR's own naming scheme to existing files — useful after you change your naming template and don't want to re-import everything.

Cancellable: yes.

renameinatorr:
  dry_run: false
  log_level: info
  rename_folders: true
  count: 100                            # total items per run (used when radarr_count/sonarr_count = 0)
  radarr_count: 0                       # override per type
  sonarr_count: 0
  tag_name: chub-renameinatorr
  ignore_tags: ""                       # comma-separated list of tags to skip
  enable_batching: false                # batch API calls for speed
  instances:
    - radarr_main
    - sonarr_main

🩺 health_checkarr

What it does. Polls each ARR's built-in health / queue / missing lists and surfaces problems via notification. report_only: true turns it into a pure notifier (no remediation).

Cancellable: yes.

health_checkarr:
  dry_run: false
  log_level: info
  report_only: false
  instances:
    - radarr_main
    - sonarr_main
    - lidarr_main

🪺 nestarr

What it does. Scans for two kinds of library problems and reports them — it never moves or deletes anything. First, it compares your ARR (Radarr / Sonarr / Lidarr) cache against Plex and flags mismatches (items in an ARR that haven't landed in Plex, and items in Plex that aren't tracked by any ARR). Second, it detects nested paths — tracked media whose folder sits inside another tracked item's folder, which usually means a botched import. You get the list; you decide what to fix.

Cancellable: yes.

nestarr:
  dry_run: false
  log_level: info
  library_mappings:                     # scope to specific ARR↔Plex library pairs
    - arr_instance: radarr_main
      plex_instances:
        - instance: plex_main
          library_names: ["Movies"]
    - arr_instance: sonarr_main
      plex_instances:
        - instance: plex_main
          library_names: ["TV Shows"]
  path_mapping:                         # translate ARR paths → Plex paths when volumes differ
    - arr_prefix: /media/movies
      plex_prefix: /data/movies

☁️ sync_gdrive

What it does. Pulls poster assets from Google Drive folders into a local directory, using rclone under the hood. Supports OAuth tokens or a service-account JSON file.

Cancellable: yes.

Gotcha: sync_location, gdrive_sa_location, and folder IDs can't contain null bytes or start with - (a safety check to keep user input from being interpreted as rclone flags).

For setup — Google service account creation, rclone OAuth flow, headless token generation — see the DAPS wiki's rclone configuration guide. CHUB uses the same rclone backend, so the steps apply unchanged.

sync_gdrive:
  dry_run: false
  log_level: info
  gdrive_sa_location: /config/gdrive-sa.json   # preferred — service account JSON
  # OR the OAuth client triple (alternative to gdrive_sa_location):
  # client_id: "<oauth-client-id>"
  # client_secret: "<oauth-client-secret>"
  # token: "<rclone-token-json>"
  gdrive_list:
    - id: "<google-drive-folder-id>"
      location: /posters/gdrive-pull
      name: "Community poster mirror"

Clone this wiki locally