Skip to content

Configuration

chodeus edited this page Jun 18, 2026 · 21 revisions

🏠 Home › Configuration

Configuration

CHUB reads one YAML file on startup — config.yml under ${CONFIG_DIR} (in Docker, /config/config.yml). This page documents the top-level structure; per-module fields live on the Modules page.

The easiest way to edit is the web UI — every Settings page writes back through a validated API. If you hand-edit the file, keep it at 0600 (it holds API keys), and note that CHUB revalidates the whole file on startup: a bad field is named in the container log and CHUB refuses to start.

On this page

Top-level blocks

Anything you omit falls back to safe defaults.

Block What it holds
general Global toggles: logging, webhook retry tuning, Plex cache TTL, dashboard layout, first-run gate.
auth Admin user (managed by the UI).
tmdb TMDB API key for ID resolution (better poster matching + request links).
fanart fanart.tv key for the fanart logo/background source.
instances Radarr / Sonarr / Lidarr / Plex connections → see First Run.
schedule When each module runs.
notifications Discord / Notifiarr per module.
user_interface Server-wide default theme.
one block per module poster_renamerr, border_replacerr, nohl, … → see Modules.

general

Global toggles. All editable from Settings → General.

Field Default Purpose
log_level info debug | info | warning | error | critical.
max_logs 9 Rotated log files kept (1–100).
update_notifications false Deprecated; no longer wired at runtime.
webhook_initial_delay 30 Seconds to wait after an inbound webhook before acting (0–3600).
webhook_retry_delay 30 Seconds between Plex-availability retries (1–3600).
webhook_max_retries 10 Max retries (0–100).
webhook_secret "" Shared secret for inbound webhooks — see Webhooks.
plex_cache_ttl_seconds 300 Reuse the Plex snapshot for this long across runs (0–86400; 0 = always refresh).
dashboard_modules [] Which module cards show, in order; empty = all.
dashboard_sections [] Which sections show (health, modules, scheduler, quick_start); empty = all.
dashboard_refresh_seconds 30 Dashboard poll/countdown interval when live updates aren't connected (0–3600; 0 = off).
dashboard_upcoming_limit 5 Upcoming runs listed in the Scheduler panel (1–50).
setup_completed false First-run wizard gate; set true once the wizard finishes (backfilled true for existing installs).
duplicate_exclude_groups [] Duplicate group IDs the UI should hide.

Gotcha: the three webhook_*_delay/retries values plus webhook_secret are documented in full on the Webhooks page — that's their canonical home.

auth

Managed by the web UI — don't hand-edit unless you're resetting things.

Field Default Purpose
username "" Admin login name.
password_hash "" Bcrypt hash (set by the UI).
jwt_secret "" Random secret signing session tokens.
token_expiry_hours 24 Session lifetime.

To reset the admin password, see Troubleshooting → Reset the admin password.

tmdb

Optional TMDB integration. When apikey is set, CHUB resolves missing tmdb_id values (via tvdb/imdb lookups) to improve poster matching, Plex GUID joins, and Unmatched-Assets request links. There's no separate toggle — match refinement is automatic when a key is present.

Field Default Purpose
apikey "" TMDB API key.
cache_expiration 60 Days to cache resolved IDs (1–3650).

fanart

Optional fanart.tv integration — the source for logos and backgrounds in asset_renamerr. A personal key is required for the fanart source (without it, asset runs fall back to local files). Get one free at fanart.tv/get-an-api-key.

Field Default Purpose
client_key "" Personal fanart.tv API key.
cache_expiration 2 Days to cache resolved logo/background URLs (1–3650).

instances

Your Radarr, Sonarr, Lidarr, and Plex connections. The key under each service (radarr_main, sonarr_4k, …) is the name you reference elsewhere in config.yml.

instances:
  radarr:
    radarr_main:
      url: http://radarr:7878
      api: <api_key>
      enabled: true
      webhook_force_reupload: false   # bypass dedup, re-push to Plex on webhook
  plex:
    plex_main:
      url: http://plex:32400
      api: <x_plex_token>             # X-Plex-Token, not a login
Field Default Purpose
url "" Service base URL CHUB can reach.
api "" API key — for Plex, the X-Plex-Token.
enabled true Whether CHUB uses this instance.
webhook_force_reupload false On radarr/sonarr/lidarr only: webhook uploads bypass the unchanged-file dedup and re-push to Plex.

Setting up and testing connections is covered on First Run (the canonical guide). In short: each row in Settings → Instances has a Test button — run it after adding or editing an entry.

Gotcha: CHUB refuses cloud-metadata addresses and unrouteable ranges as a safety check. Use a normal IP or hostname your container can resolve (http://radarr:7878 works on a shared Docker network).

schedule

One entry per module that should run on a schedule. Anything not listed is manual-only (run it from the dashboard or via a webhook). Settings → Schedule writes this for you.

schedule:
  poster_renamerr:
    type: cron
    expression: "0 */4 * * *"   # every 4 hours
  jduparr:
    type: interval
    minutes: 720                # every 12 hours

type is cron (with expression) or interval (with minutes). A built-in system health probe runs every 6 hours on its own — you don't configure it.

notifications

One entry per module that should notify, plus an optional main catch-all. Two services through the UI: Discord (channel webhook) and Notifiarr (Passthrough).

notifications:
  main:
    discord:
      bot_name: CHUB Errors
      color: "#FF0000"
      webhook: https://discord.com/api/webhooks/...
  poster_renamerr:
    notifiarr:
      color: "#5865F2"
      webhook: https://notifiarr.com/api/v1/notification/passthrough/<API_KEY>
      channel_id: 1234567890
Field Applies to Purpose
webhook both Discord channel webhook URL, or Notifiarr Passthrough URL.
bot_name Discord only Overrides the webhook's default username.
color both Hex embed color. Error notifications are always red.
channel_id Notifiarr Discord channel ID for Notifiarr to post to.

Each configured module sends one notification per run (even no-ops). The main entry is the catch-all error channel — any ERROR-level log line from any module. Each entry's Test button uses the real notification path, so a passing test means production works too.

For full Discord/Notifiarr setup steps and per-module notification content, see UI Guide and the in-app Settings → Notifications forms.

user_interface

user_interface:
  theme: dark    # light | dark

Server-wide default. Each browser also remembers its own choice, so the header toggle sticks per device.

Secret handling

When CHUB returns config to the UI, these fields are replaced with ******** so they don't leak into browser storage or screenshots. Redaction matches exact leaf key names:

api, api_key, apikey, client_key, access_token, refresh_token, client_secret, token, password_hash, jwt_secret, webhook_secret, and outbound notification webhook URLs.

When you save config back through the UI, any field still equal to ******** is kept as-is — editing other fields won't wipe your secrets.

Gotcha: matching is by exact key name. The webhook_force_reupload and webhook_*_delay fields are not redacted (only the literal webhook and webhook_secret keys are).

If CHUB won't start

The container log prints which field failed validation. Fix it or remove the bad section and restart — CHUB defaults anything missing.

docker compose logs chub

See Troubleshooting for common failures.

Appendix: full example + legacy migration

Full example config.yml

A complete, working example with every top-level block populated and each module enabled minimally. Per-module fields are documented on Modules.

general:
  log_level: info
  max_logs: 9
  webhook_initial_delay: 30
  webhook_retry_delay: 30
  webhook_max_retries: 10
  webhook_secret: ""
  plex_cache_ttl_seconds: 300
  duplicate_exclude_groups: []

auth:
  username: admin                  # managed by the UI; leave the rest to CHUB
  password_hash: ""
  jwt_secret: ""
  token_expiry_hours: 24

tmdb:
  apikey: ""
  cache_expiration: 60

fanart:
  client_key: ""
  cache_expiration: 2

instances:
  radarr:
    radarr_main:
      url: http://radarr:7878
      api: <radarr-api-key>
      enabled: true
  sonarr:
    sonarr_main:
      url: http://sonarr:8989
      api: <sonarr-api-key>
      enabled: true
  lidarr:
    lidarr_main:
      url: http://lidarr:8686
      api: <lidarr-api-key>
      enabled: true
  plex:
    plex_main:
      url: http://plex:32400
      api: <x-plex-token>
      enabled: true

schedule:
  poster_renamerr:
    type: cron
    expression: "0 */4 * * *"      # every 4 hours
  jduparr:
    type: interval
    minutes: 720                   # every 12 hours
  upgradinatorr:
    type: cron
    expression: "15 3 * * *"       # daily at 03:15

notifications:
  main:
    discord:
      webhook: https://discord.com/api/webhooks/...
  health_checkarr:
    notifiarr:
      webhook: https://notifiarr.com/api/v1/notification/passthrough/<API_KEY>
      channel_id: 1234567890

user_interface:
  theme: dark                      # light | dark

# --- Modules (minimal subset; see the Modules page for every field) ---
poster_renamerr:
  dry_run: false
  apply_method: kometa             # kometa | plex
  action_type: hardlink            # copy | move | hardlink | symlink
  asset_folders: true
  source_dirs: [/kometa]
  destination_dir: /posters
  instances:
    - radarr_main
    - sonarr_main
    - plex_main:
        library_names: ["Movies", "TV Shows"]
        add_posters: true          # only used when apply_method: plex

border_replacerr:
  dry_run: false
  source_dirs: [/posters]
  destination_dir: /posters
  border_width: 26                 # 0–200; color-mode only
  border_colors: ["#ff7300"]
  holidays:
    - name: 🎃 Halloween
      schedule: "range(10/01-10/31)"
      colors: ["#FF6600", "#000000"]

poster_cleanarr:
  mode: report                     # bloat: report | move | remove | restore | clear | nothing
  plex_path: "/plex-config/Library/Application Support/Plex Media Server/Metadata"
  instances: [plex_main, radarr_main, sonarr_main]
  orphan_assets_enabled: false
  orphan_assets_mode: report       # report | move | remove
  asset_dirs: []
  stale_duplicates_enabled: false
  stale_duplicates_mode: report    # report | move | remove

jduparr:
  hash_database: /config/jduparr.db
  source_dirs: [/media/movies, /media/tv]

nohl:
  searches: 10
  source_dirs:
    - { path: /media/movies, mode: resolve }   # scan | resolve
    - { path: /media/tv,     mode: resolve }
  instances: [radarr_main, sonarr_main]

upgradinatorr:
  instances_list:
    - instance: radarr_main
      count: 10
      tag_name: chub-upgradinatorr
      search_mode: upgrade

renameinatorr:
  rename_folders: true
  count: 100                       # integer, or "" = all items
  tag_name: chub-renameinatorr
  instances: [radarr_main, sonarr_main]

health_checkarr:
  dry_run: true                    # deletes ARR items — start dry
  instances: [radarr_main, sonarr_main]

nestarr:
  library_mappings:
    - arr_instance: radarr_main
      plex_instances:
        - { instance: plex_main, library_names: [Movies] }
  path_mapping:
    - arr_path: /media/movies
      local_path: /data/movies

sync_gdrive:
  gdrive_sa_location: /config/gdrive-sa.json
  gdrive_list:
    - id: "<google-drive-folder-id>"
      location: /posters/gdrive-pull
Auto-migration from DAPS / older config formats

If you drop in a config.yml from DAPS (or an older CHUB layout), CHUB detects the legacy shape on load and migrates it in place. This is config-shape migration only — there's no database or match-state migration (CHUB is still a clean install; see FAQ). On startup, if legacy signals are present:

  1. The original is copied to config.yml.legacy-<timestamp>.yml (kept untouched).
  2. Migration rules run against the parsed YAML in memory.
  3. The migrated YAML is written back to config.yml.
  4. Each rule that fired is logged.

If the file already matches the current schema, none of this happens. Re-runs are idempotent.

What gets migrated

Kind Example
Section moves main.log_levelgeneral.log_level; main.themeuser_interface.theme; empty main: dropped
Section removals Top-level discord: (per-module notifications cover it)
Field renames unmatched_assets.ignore_root_foldersignore_folders; renameinatorr.ignore_tagignore_tags; poster_cleanarr.source_dirsasset_dirs; labelarr plex_instanceinstance
Field removals poster_renamerr.incremental_border_replacerr, unmatched_assets.source_dirs, poster_cleanarr.ignore_media (warnings logged)
Type conversions poster_cleanarr.dry_run: boolmode: str; border_replacerr.holidays: dictlist

Verify paths after migration. The migrator rewrites only the shape of the YAML — it can't know your host mounts. Spot-check path-valued fields after first boot: sync_gdrive.gdrive_sa_location + gdrive_list[].location, poster_renamerr.source_dirs[] + destination_dir, border_replacerr.source_dirs[] + destination_dir, poster_cleanarr.asset_dirs[] + plex_path, plex_maintenance.plex_path, nohl.source_dirs[].path, jduparr.hash_database + source_dirs[].

Restore the original: stop CHUB, copy config.yml.legacy-<timestamp>.yml over config.yml, restart.


Next: Modules · Related: First Run, Webhooks

Clone this wiki locally