#!/bin/bash # ============================================================================================== # ================================= Arr Cache Prefill =========================================== # ============================================================================================== # # PURPOSE # ───────────────────────────────────────────────────────────────────────────── # Populates the shared tracked-data cache (see arr_get_tracked_data() in common.sh) for # Lidarr, Sonarr, and Radarr. Without this, each arr's cache stays cold until whichever # script happens to touch that arr first writes through — which could be hours, depending # on the daily schedule. Originally Lidarr-only (lidarr_cache_prefill.sh, 2026-07-16), # generalized the same day to cover all three arrs once the cache mechanism itself was # generalized. # # Runs on two schedules (2026-07-17): once at array start (closes the cold-boot gap, 10min # wait ceiling for slow-starting containers) and again every 30min via # CRITICAL_MAINTENANCE_SCRIPTS with a 1min wait ceiling (ARR_PREFILL_WAIT_MINUTES=1 override) # — a live fetch+write takes seconds, so there's no reason to tolerate the boot-time wait on # a recurring job. This is what keeps the cache-first consumers' data reliably under 30min # old instead of only refreshing whenever some other script happens to write through. # # ============================================================================================== # OPERATIONAL MODEL # ============================================================================================== # # Each arr's container may still be starting when this fires (array just started) — retries # reaching that arr's API for up to ARR_PREFILL_WAIT_MINUTES before giving up on it and moving # to the next. Not fatal if one never comes up in time; that arr's cache just stays cold until # the next script writes through naturally, exactly as it would without this script existing. # An arr not configured on this host (e.g. Lidarr is HOST1-only) is skipped cleanly. # # ============================================================================================== # DESIGN PRINCIPLES # ============================================================================================== # # Warm the Cache, Never Own It # This script only pre-populates what arr_get_tracked_data() would fetch on demand # anyway. Nothing depends on it having run — every consumer still writes through on a # cold cache. It removes latency and staleness, it is not a dependency. # # Failure Is a No-Op, Not an Error # An arr that never comes up in time simply leaves its cache cold, exactly as if this # script did not exist. That is why a missed prefill is logged rather than notified — # the fallback path is the normal path. # # Wait Ceiling Matched to the Trigger # The array-start run tolerates a long wait because containers are genuinely still # starting. The 30-minute recurring run does not, because a live fetch takes seconds # and a long wait there would only serve to overlap the next tick. # # Per-Arr Independence # Each arr is prefilled on its own. One unconfigured or slow-starting arr never # prevents the other two from being warmed. # # ============================================================================================== # OPERATIONAL SAFEGUARDS # ============================================================================================== # # Root required — chown-free here, but matches convention across Arrs_Stack/ # jq required — skips the whole run cleanly (not fatal) if jq is missing # acquire_lock — prevents two invocations of this script overlapping (default strict # mode) — matters if the boot-time run is still waiting on a slow # container when the first 30min critical-tier tick fires # Reachability retry — tolerates a slow-starting container up to ARR_PREFILL_WAIT_MINUTES; # never fatal if one never comes up, that arr's cache just stays cold # Active-rescan check — skips an arr this cycle if a rescan-type command is running, instead # of a live fetch arr_cache_write() would refuse to persist anyway # (2026-07-17) — avoids wasted API calls during a long rescan # Per-arr isolation — one arr failing or timing out never blocks or fails the others # # ============================================================================================== # CONFIGURATION # ============================================================================================== # # host*.conf # HOST1_LIDARR_URL / HOST1_LIDARR_API_KEY # HOST*_SONARR_URL / HOST*_SONARR_API_KEY # HOST*_RADARR_URL / HOST*_RADARR_API_KEY # All aliased by detect_hosts() # # master.conf # ARR_PREFILL_WAIT_MINUTES — how long to retry reaching each arr before giving up on it # (default 10). The CRITICAL_MAINTENANCE_SCRIPTS entry overrides this to 1 via parse_args' # VAR=VAL mechanism for the 30min recurring run — the 10min default is sized for cold boot, # not a job that fires every half hour. # # ============================================================================================== # RUNTIME MODES # ============================================================================================== # # arr_cache_prefill.sh # Normal run — populates all three arr caches, or skips cleanly per-arr as described # under OPERATIONAL SAFEGUARDS above. No --dry-run/--status mode: this script only ever # reads and writes cache, there's no destructive action to preview and no separate state # worth inspecting beyond the cache files themselves (see Tools/arr_rescan_monitor.sh # --status for cache age/active-rescan inspection). # # arr_cache_prefill.sh --log # Verbose — per-arr detail as each one is checked/fetched/skipped. # # arr_cache_prefill.sh ARR_PREFILL_WAIT_MINUTES=1 # Override the reachability-retry ceiling for this run only (parse_args VAR=VAL # mechanism) — this is how CRITICAL_MAINTENANCE_SCRIPTS invokes it every 30min. # # ============================================================================================== SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "$SCRIPT_DIR/../load_config.sh" parse_args "$@" if [[ "$EUID" -ne 0 ]]; then error "Must be run as root" exit 1 fi if ! command -v jq >/dev/null 2>&1; then warn "jq not found — skipping arr cache prefill" exit 0 fi acquire_lock detect_hosts WAIT_MINUTES="${ARR_PREFILL_WAIT_MINUTES:-10}" # Args: arr_type, url, api_key, api_version _prefill_one() { local arr_type="$1" url="$2" api_key="$3" api_version="$4" if [[ -z "$url" ]] || [[ -z "$api_key" ]]; then info "${arr_type^} not configured on $MY_ID ($LOCAL_SERVER_NAME) — nothing to prefill" return 0 fi local waited=0 until check_api "$url" "${arr_type^}" 5 >/dev/null 2>&1; do if [[ "$waited" -ge $(( WAIT_MINUTES * 60 )) ]]; then warn "${arr_type^} not reachable after ${WAIT_MINUTES}m — leaving cache cold, next script will write through" return 0 fi sleep 15 (( waited += 15 )) done # Skip cleanly if a rescan-type command is already active — arr_cache_write() below # would refuse to persist a mid-rescan snapshot anyway (2026-07-17 guard), so fetching # it live first would just be a wasted API call every time this fires during a long # rescan. Matches the same check arr_full_rescan.sh and the cleanup scripts already do. local active_cmd active_cmd=$(arr_active_rescan_command "$arr_type" "$url" "$api_key" "$api_version") if [[ -n "$active_cmd" ]]; then info "${arr_type^} mid-rescan ($active_cmd) — skipping this cycle, cache stays as-is" return 0 fi local endpoint="${ARR_LIBRARY_ENDPOINT[$arr_type]:-}" if [[ -z "$endpoint" ]]; then warn "No library endpoint known for ${arr_type} — skipping" return 0 fi local items items=$(arr_api "$url" "$api_key" "$api_version" "$endpoint" "${arr_type^}") || { warn "Could not fetch ${arr_type} library for cache prefill — leaving cache cold" return 0 } if arr_cache_write "$arr_type" "$items"; then log "$ICON_DONE ${arr_type^} cache prefilled ($(echo "$items" | jq 'length') items)" else warn "Failed to write ${arr_type} cache prefill" fi } _prefill_one "lidarr" "${LIDARR_URL:-}" "${LIDARR_API_KEY:-}" "v1" _prefill_one "sonarr" "${SONARR_URL:-}" "${SONARR_API_KEY:-}" "v3" _prefill_one "radarr" "${RADARR_URL:-}" "${RADARR_API_KEY:-}" "v3" exit 0