#!/bin/bash # ============================================================================================== # ============================= Conf Auto-Populate ============================================= # ============================================================================================== # # PURPOSE # ───────────────────────────────────────────────────────────────────────────── # Reads credentials and settings from locally running services and writes # them into the local host conf. Safe to run multiple times — only populates # EMPTY fields, never overwrites existing values unless --overwrite is passed. # # After populating, pushes the updated conf to all partners via conf_sync.sh # so they have the fresh keys in their /tmp/.vv/ cache immediately. # # ============================================================================================== # AUTO-DETECTED FIELDS # ============================================================================================== # # HOSTN_RADARR_API_KEY from Radarr config.xml (found via docker volume mount) # HOSTN_SONARR_API_KEY from Sonarr config.xml # HOSTN_LIDARR_API_KEY from Lidarr config.xml # HOSTN_SLSKD_API_KEY from slskd config.yml # HOSTN_SABNZBD_API_KEY from sabnzbd.ini # HOSTN_EMBY_CONTAINER fuzzy match from docker ps # HOSTN_JELLYFIN_CONTAINER fuzzy match from docker ps # HOSTN_RADARR_MOVIE_ROOT from Radarr rootFolder API # HOSTN_SONARR_TV_ROOT from Sonarr rootFolder API # HOSTN_LIDARR_MUSIC_ROOT from Lidarr rootFolder API # HOSTN_SYS_WATCHDOG_NIC from ip route default gateway interface # # ============================================================================================== # RUNTIME MODES # ============================================================================================== # # conf_populate.sh Populate empty fields only # conf_populate.sh --overwrite Overwrite all detected fields (re-sync after arr key rotation) # conf_populate.sh --dry-run Show what would be written, no changes # conf_populate.sh --log Verbose output # conf_populate.sh --no-push Skip pushing to partners after update # # ============================================================================================== SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "$SCRIPT_DIR/../load_config.sh" OVERWRITE=false NO_PUSH=false FILTERED_ARGS=() for arg in "$@"; do case "$arg" in --overwrite) OVERWRITE=true ;; --no-push) NO_PUSH=true ;; *) FILTERED_ARGS+=("$arg") ;; esac done parse_args "${FILTERED_ARGS[@]}" detect_hosts [[ "$EUID" -ne 0 ]] && { error "Must be run as root"; exit 1; } CONF_FILE="$SCRIPTS_ROOT/Configurations/${MY_ID,,}.conf" [[ ! -f "$CONF_FILE" ]] && { error "Conf file not found: $CONF_FILE"; exit 1; } UPDATED=0 SKIPPED=0 echo "" echo "━━━ $ICON_GEAR Conf Auto-Populate — $MY_ID ($LOCAL_SERVER_NAME) ━━━" echo "" [[ "$DRY_RUN" == true ]] && warn "DRY RUN — no changes will be made" [[ "$OVERWRITE" == true ]] && warn "OVERWRITE mode — existing values will be replaced" # ── Helper: write a var into conf if empty (or --overwrite) ────────────────── _set_conf_var() { local var_name="$1" value="$2" label="$3" [[ -z "$value" ]] && return # Check current value in conf local current current=$(grep -oP "(?<=^\s*${var_name}=\")[^\"]*" "$CONF_FILE" 2>/dev/null | head -1) if [[ -n "$current" ]] && [[ "$OVERWRITE" == false ]]; then log "$label: already set (${current:0:8}…) — skipping" (( SKIPPED++ )) return fi if [[ "$DRY_RUN" == true ]]; then warn "DRY RUN — would set $var_name = ${value:0:8}…" return fi # Update or append the var line if grep -q "^\s*${var_name}=" "$CONF_FILE"; then sed -i "s|^\(\s*${var_name}\s*=\s*\)\"[^\"]*\"|\1\"${value}\"|" "$CONF_FILE" else printf '\n %s="%s"\n' "$var_name" "$value" >> "$CONF_FILE" fi info "$label: set ✅" (( UPDATED++ )) } # ── Helper: find arr config dir via docker volume mount ─────────────────────── # Looks for a container matching the pattern, then reads its /config volume path. # Falls back to DOCKER_APPDATA_BASE/ if volume not found. _arr_config_dir() { local pattern="$1" local container_name container_name=$(docker ps -a --format '{{.Names}}' 2>/dev/null | \ grep -im1 "^${pattern}") [[ -z "$container_name" ]] && return 1 local config_path config_path=$(docker inspect "$container_name" 2>/dev/null | \ jq -r '.[0].Mounts[]? | select(.Destination == "/config") | .Source' 2>/dev/null | head -1) [[ -z "$config_path" ]] && config_path="${DOCKER_APPDATA_BASE:-/mnt/user/appdata}/${container_name}" [[ -d "$config_path" ]] && echo "$config_path" || return 1 } # ── Helper: read XML tag value ──────────────────────────────────────────────── _xml_val() { local file="$1" tag="$2" grep -oP "(?<=<${tag}>)[^<]+" "$file" 2>/dev/null | head -1 } # ============================================================================================== # ── Arr API keys + root paths ───────────────────────────────────────────────────────────────── # ============================================================================================== for arr in radarr sonarr lidarr; do arr_upper="${arr^^}" config_dir=$(_arr_config_dir "$arr") || { log "${arr_upper}: no running container found — skipping" continue } config_xml="${config_dir}/config.xml" if [[ ! -f "$config_xml" ]]; then log "${arr_upper}: config.xml not found at $config_xml — skipping" continue fi key=$(_xml_val "$config_xml" "ApiKey") port=$(_xml_val "$config_xml" "Port") url_base="http://localhost:${port:-$(case $arr in radarr) echo 7878;; sonarr) echo 8989;; lidarr) echo 8686;; esac)}" _set_conf_var "${MY_ID}_${arr_upper}_API_KEY" "$key" "${arr_upper} API key" # Root paths from arr's own rootFolder API if [[ -n "$key" ]]; then local api_ver; case "$arr" in lidarr) api_ver="v1" ;; *) api_ver="v3" ;; esac root_json=$(curl -sf --max-time 5 \ -H "X-Api-Key: $key" "${url_base}/api/${api_ver}/rootfolder" 2>/dev/null) root_path=$(echo "$root_json" | jq -r '.[0].path // empty' 2>/dev/null) case "$arr" in radarr) _set_conf_var "${MY_ID}_RADARR_MOVIE_ROOT" "$root_path" "Radarr movie root" ;; sonarr) _set_conf_var "${MY_ID}_SONARR_TV_ROOT" "$root_path" "Sonarr TV root" ;; lidarr) _set_conf_var "${MY_ID}_LIDARR_MUSIC_ROOT" "$root_path" "Lidarr music root" ;; esac fi done # ============================================================================================== # ── SABnzbd API key ─────────────────────────────────────────────────────────────────────────── # ============================================================================================== sab_dir=$(_arr_config_dir "sabnzbd") && { sab_ini=$(find "$sab_dir" -maxdepth 2 -name "sabnzbd.ini" 2>/dev/null | head -1) if [[ -f "$sab_ini" ]]; then sab_key=$(grep -oP '(?<=^api_key\s*=\s*)\S+' "$sab_ini" 2>/dev/null | head -1) _set_conf_var "${MY_ID}_SABNZBD_API_KEY" "$sab_key" "SABnzbd API key" fi } # ============================================================================================== # ── slskd API key ───────────────────────────────────────────────────────────────────────────── # ============================================================================================== slskd_dir=$(_arr_config_dir "slskd") && { slskd_yml=$(find "$slskd_dir" -maxdepth 2 -name "*.yml" -o -name "*.yaml" 2>/dev/null | head -1) if [[ -f "$slskd_yml" ]]; then slskd_key=$(grep -oP '(?<=api_key:\s)[\w-]+' "$slskd_yml" 2>/dev/null | head -1) [[ -z "$slskd_key" ]] && \ slskd_key=$(grep -oP '(?<=apikey:\s)[\w-]+' "$slskd_yml" 2>/dev/null | head -1) _set_conf_var "${MY_ID}_SLSKD_API_KEY" "$slskd_key" "slskd API key" fi } # ============================================================================================== # ── Container names ─────────────────────────────────────────────────────────────────────────── # ============================================================================================== for pattern in "emby" "jellyfin"; do container=$(docker ps -a --format '{{.Names}}' 2>/dev/null | grep -im1 "^${pattern}") [[ -z "$container" ]] && continue case "$pattern" in emby) _set_conf_var "${MY_ID}_EMBY_CONTAINER" "$container" "Emby container name" ;; jellyfin) _set_conf_var "${MY_ID}_JELLYFIN_CONTAINER" "$container" "Jellyfin container name" ;; esac done # ============================================================================================== # ── Network interface ───────────────────────────────────────────────────────────────────────── # ============================================================================================== nic=$(ip route show default 2>/dev/null | grep -oP '(?<=dev )\S+' | head -1) _set_conf_var "${MY_ID}_SYS_WATCHDOG_NIC" "$nic" "Default NIC" # ============================================================================================== # ── Summary + push ──────────────────────────────────────────────────────────────────────────── # ============================================================================================== echo "" echo "━━━━━ $ICON_SUMMARY Conf Populate Summary ━━━━━" echo " Updated: $UPDATED field(s)" echo " Skipped: $SKIPPED already set" echo " Conf: $CONF_FILE" echo "━━━━━━━━━━━━━━━━━━━━━" if [[ "$UPDATED" -gt 0 ]] && [[ "$DRY_RUN" == false ]] && [[ "$NO_PUSH" == false ]]; then echo "" info "Pushing updated conf to partners..." bash "$SCRIPTS_ROOT/System_Essentials/conf_sync.sh" --push-only "${EXTRA_FLAGS[@]}" || true fi