From ac2986b141c7cf8a06aaaa29e114e909ad322eba Mon Sep 17 00:00:00 2001 From: Gmer4Lfe Date: Sun, 14 Jun 2026 22:42:44 -0400 Subject: [PATCH] Extend conf_populate.sh with all auto-detectable fields MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds SSH key (hostname convention), arr path maps (docker volume mounts), Authelia container + config path, boot device storage mode detection, and master.conf HOST identity + Gitea container. Also fixes RADARR_MOVIE_ROOT → RADARR_MOVIES_ROOT to match the variable name used by all other scripts. --- Deployment/conf_populate.sh | 171 ++++++++++++++++++++++++++++++++---- 1 file changed, 152 insertions(+), 19 deletions(-) diff --git a/Deployment/conf_populate.sh b/Deployment/conf_populate.sh index 8d15e3a..0e41b11 100755 --- a/Deployment/conf_populate.sh +++ b/Deployment/conf_populate.sh @@ -16,16 +16,23 @@ # AUTO-DETECTED FIELDS # ============================================================================================== # +# HOST CONF (Configurations/.conf) +# ────────────────────────────────────────────────────────────────────────── # HOSTN_OWNER from hostname (strip unRAID- prefix, lowercase) +# HOSTN_SSH_KEY from hostname convention (/root/.ssh/_rsync_automation) +# HOSTN_STORAGE_MODE_INTERNAL from boot device transport (NVMe/SSD=true, USB=false) # HOSTN_RADARR_API_KEY from Radarr config.xml (found via docker volume mount) # HOSTN_RADARR_URL from Radarr config.xml port +# HOSTN_RADARR_MOVIES_ROOT from Radarr rootFolder API +# HOSTN_RADARR_PATH_MAP from Radarr docker volume mounts vs root folder path # HOSTN_SONARR_API_KEY from Sonarr config.xml # HOSTN_SONARR_URL from Sonarr config.xml port +# HOSTN_SONARR_TV_ROOT from Sonarr rootFolder API +# HOSTN_SONARR_PATH_MAP from Sonarr docker volume mounts vs root folder path # HOSTN_LIDARR_API_KEY from Lidarr config.xml # HOSTN_LIDARR_URL from Lidarr config.xml port -# 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_LIDARR_PATH_MAP from Lidarr docker volume mounts vs root folder path # HOSTN_SABNZBD_API_KEY from sabnzbd.ini # HOSTN_SABNZBD_URL from sabnzbd.ini port # HOSTN_SLSKD_API_KEY from slskd config.yml @@ -38,7 +45,14 @@ # HOSTN_JELLYFIN_CONTAINER fuzzy match from docker ps # HOSTN_JELLYFIN_URL from docker port binding # HOSTN_TRANSCODE_SSD from Emby/Jellyfin container /transcode volume mount -# HOSTN_SYS_WATCHDOG_NIC from ip route default gateway interface +# HOSTN_AUTHELIA_CONTAINER fuzzy match from docker ps +# HOSTN_AUTHELIA_CONFIG from Authelia container /config volume mount +# HOSTN_SYS_WATCHDOG_NIC from ip route default gateway interface +# +# MASTER CONF (Configurations/master.conf) +# ────────────────────────────────────────────────────────────────────────── +# HOST1 / HOST2 local hostname written to MY_ID slot +# GITEA_CONTAINER fuzzy match from docker ps # # ============================================================================================== # RUNTIME MODES @@ -74,6 +88,8 @@ detect_hosts CONF_FILE="$SCRIPTS_ROOT/Configurations/${MY_ID,,}.conf" [[ ! -f "$CONF_FILE" ]] && { error "Conf file not found: $CONF_FILE"; exit 1; } +MASTER_CONF="$SCRIPTS_ROOT/Configurations/master.conf" + log "$ICON_GEAR Config: conf=${CONF_FILE} overwrite=${OVERWRITE:-false} no-push=${NO_PUSH:-false}" UPDATED=0 @@ -85,13 +101,14 @@ 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) ────────────────── +# ── Helper: write a var into a conf file if empty (or --overwrite) ──────────── +# Optional 4th arg: target file (defaults to $CONF_FILE) _set_conf_var() { - local var_name="$1" value="$2" label="$3" + local var_name="$1" value="$2" label="$3" target="${4:-$CONF_FILE}" [[ -z "$value" ]] && return local current - current=$(grep -oP "(?<=^\s*${var_name}=\")[^\"]*" "$CONF_FILE" 2>/dev/null | head -1) + current=$(grep -oP "(?<=^\s*${var_name}=\")[^\"]*" "$target" 2>/dev/null | head -1) if [[ -n "$current" ]] && [[ "$OVERWRITE" == false ]]; then log "$label: already set (${current:0:8}…) — skipping" @@ -104,15 +121,59 @@ _set_conf_var() { return fi - if grep -q "^\s*${var_name}=" "$CONF_FILE"; then - sed -i "s|^\(\s*${var_name}\s*=\s*\)\"[^\"]*\"|\1\"${value}\"|" "$CONF_FILE" + if grep -q "^\s*${var_name}=" "$target"; then + sed -i "s|^\(\s*${var_name}\s*=\s*\)\"[^\"]*\"|\1\"${value}\"|" "$target" else - printf '\n %s="%s"\n' "$var_name" "$value" >> "$CONF_FILE" + printf '\n %s="%s"\n' "$var_name" "$value" >> "$target" fi info "$label: set ✅" (( UPDATED++ )) } +# ── Helper: add or update an entry in a declare -A map block ────────────────── +# Inserts [key]="value" before the closing ) of the named map in $CONF_FILE. +# Skips if an identical uncommented entry already exists (unless --overwrite). +_set_conf_map_entry() { + local map_name="$1" key="$2" value="$3" label="$4" + [[ -z "$key" || -z "$value" ]] && return + + if grep -qP "^\s*\[\"${key//\//\\/}\"\]=" "$CONF_FILE" 2>/dev/null; then + if [[ "$OVERWRITE" == false ]]; then + log "$label: already set — skipping" + (( SKIPPED++ )) + return + fi + [[ "$DRY_RUN" == true ]] && { warn "DRY RUN — would update ${map_name}[\"${key}\"]"; return; } + sed -i "s|^\(\s*\)\[\"${key}\"\]=\"[^\"]*\"|\1[\"${key}\"]=\"${value}\"|" "$CONF_FILE" + info "$label: set ✅" + (( UPDATED++ )) + return + fi + + if ! grep -q "declare -A ${map_name}=" "$CONF_FILE" 2>/dev/null; then + warn "$label: ${map_name} not found in conf — skipping" + return + fi + + [[ "$DRY_RUN" == true ]] && { warn "DRY RUN — would set ${map_name}[\"${key}\"] = \"${value}\""; return; } + + awk -v mapname="${map_name}" -v key="$key" -v val="$value" ' + BEGIN { in_map=0; done=0 } + !done && index($0, "declare -A " mapname) { in_map=1 } + in_map && !done && /^\s*\)/ { + printf " [\"%s\"]=\"%s\"\n", key, val + done=1; in_map=0 + } + { print } + ' "$CONF_FILE" > "${CONF_FILE}.tmp" && mv "${CONF_FILE}.tmp" "$CONF_FILE" || { + rm -f "${CONF_FILE}.tmp" + warn "$label: failed to update ${map_name}" + return 1 + } + info "$label: set ✅" + (( UPDATED++ )) +} + # ── Helper: find arr config dir via docker volume mount ─────────────────────── _arr_config_dir() { local pattern="$1" @@ -150,19 +211,35 @@ _docker_volume_host() { '.[0].Mounts[]? | select(.Destination == $d) | .Source' 2>/dev/null | head -1 } +# ── Helper: find host↔container path mapping for a given container path ─────── +# Finds the most specific mount whose destination is a prefix of container_path +# and where source != destination (i.e. an actual remapping exists). +# Outputs "source|destination" or nothing if no remapping found. +_docker_path_map() { + local container="$1" container_path="$2" + docker inspect "$container" 2>/dev/null | \ + jq -r '.[0].Mounts[]? | select(.Destination != "/config") | "\(.Source)|\(.Destination)"' \ + 2>/dev/null | \ + awk -F'|' -v target="$container_path" ' + $1 != $2 && length($2) > 0 && index(target, $2) == 1 { print length($2), $0 } + ' | sort -rn | head -1 | cut -d' ' -f2- +} + # ============================================================================================== -# ── Owner short name ────────────────────────────────────────────────────────────────────────── +# ── Owner short name + SSH key ──────────────────────────────────────────────────────────────── # ============================================================================================== owner=$(echo "$LOCAL_SERVER_NAME" | sed 's/^[Uu][Nn][Rr][Aa][Ii][Dd]-//i' | tr '[:upper:]' '[:lower:]') -_set_conf_var "${MY_ID}_OWNER" "$owner" "Owner short name" +_set_conf_var "${MY_ID}_OWNER" "$owner" "Owner short name" +_set_conf_var "${MY_ID}_SSH_KEY" "/root/.ssh/${owner}_rsync_automation" "SSH key path" # ============================================================================================== -# ── Arr API keys + URLs + root paths ───────────────────────────────────────────────────────── +# ── Arr API keys + URLs + root paths + path maps ────────────────────────────────────────────── # ============================================================================================== for arr in radarr sonarr lidarr; do arr_upper="${arr^^}" + arr_container=$(docker ps -a --format '{{.Names}}' 2>/dev/null | grep -im1 "^${arr}") config_dir=$(_arr_config_dir "$arr") || { log "${arr_upper}: no running container found — skipping" continue @@ -189,10 +266,23 @@ for arr in radarr sonarr lidarr; do 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" ;; + radarr) _set_conf_var "${MY_ID}_RADARR_MOVIES_ROOT" "$root_path" "Radarr movies 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 + + if [[ -n "$root_path" && -n "$arr_container" ]]; then + map_entry=$(_docker_path_map "$arr_container" "$root_path") + if [[ -n "$map_entry" ]]; then + map_src="${map_entry%%|*}" + map_dest="${map_entry##*|}" + case "$arr" in + radarr) _set_conf_map_entry "${MY_ID}_RADARR_PATH_MAP" "$map_dest" "$map_src" "Radarr path map" ;; + sonarr) _set_conf_map_entry "${MY_ID}_SONARR_PATH_MAP" "$map_dest" "$map_src" "Sonarr path map" ;; + lidarr) _set_conf_map_entry "${MY_ID}_LIDARR_PATH_MAP" "$map_dest" "$map_src" "Lidarr path map" ;; + esac + fi + fi fi done @@ -205,7 +295,7 @@ sab_dir=$(_arr_config_dir "sabnzbd") && { if [[ -f "$sab_ini" ]]; then sab_key=$(grep -oP '(?<=^api_key\s*=\s*)\S+' "$sab_ini" 2>/dev/null | head -1) sab_port=$(grep -oP '(?<=^port\s*=\s*)\d+' "$sab_ini" 2>/dev/null | head -1) - _set_conf_var "${MY_ID}_SABNZBD_API_KEY" "$sab_key" "SABnzbd API key" + _set_conf_var "${MY_ID}_SABNZBD_API_KEY" "$sab_key" "SABnzbd API key" _set_conf_var "${MY_ID}_SABNZBD_URL" "http://localhost:${sab_port:-8080}" "SABnzbd URL" fi } @@ -221,7 +311,7 @@ slskd_dir=$(_arr_config_dir "slskd") && { [[ -z "$slskd_key" ]] && \ slskd_key=$(grep -oP '(?<=apikey:\s)[\w-]+' "$slskd_yml" 2>/dev/null | head -1) slskd_port=$(grep -oP '(?<=port:\s)\d+' "$slskd_yml" 2>/dev/null | head -1) - _set_conf_var "${MY_ID}_SLSKD_API_KEY" "$slskd_key" "slskd API key" + _set_conf_var "${MY_ID}_SLSKD_API_KEY" "$slskd_key" "slskd API key" _set_conf_var "${MY_ID}_SLSKD_URL" "http://localhost:${slskd_port:-5030}" "slskd URL" fi } @@ -257,8 +347,8 @@ for pattern in "emby" "jellyfin"; do case "$pattern" in emby) host_port=$(_docker_host_port "$container" "8096") - _set_conf_var "${MY_ID}_EMBY_CONTAINER" "$container" "Emby container name" - _set_conf_var "${MY_ID}_EMBY_URL" "http://localhost:${host_port:-8096}" "Emby URL" + _set_conf_var "${MY_ID}_EMBY_CONTAINER" "$container" "Emby container name" + _set_conf_var "${MY_ID}_EMBY_URL" "http://localhost:${host_port:-8096}" "Emby URL" ;; jellyfin) host_port=$(_docker_host_port "$container" "8096") @@ -276,6 +366,18 @@ done [[ -n "$transcode_dir" ]] && \ _set_conf_var "${MY_ID}_TRANSCODE_SSD" "${transcode_dir%/}/" "Transcode SSD path" +# ============================================================================================== +# ── Authelia container + config path ────────────────────────────────────────────────────────── +# ============================================================================================== + +authelia_container=$(docker ps -a --format '{{.Names}}' 2>/dev/null | grep -im1 "authelia") +if [[ -n "$authelia_container" ]]; then + _set_conf_var "${MY_ID}_AUTHELIA_CONTAINER" "$authelia_container" "Authelia container" + authelia_cfg_dir=$(_docker_volume_host "$authelia_container" "/config") + [[ -n "$authelia_cfg_dir" ]] && \ + _set_conf_var "${MY_ID}_AUTHELIA_CONFIG" "${authelia_cfg_dir}/configuration.yml" "Authelia config path" +fi + # ============================================================================================== # ── Network interface ───────────────────────────────────────────────────────────────────────── # ============================================================================================== @@ -283,6 +385,37 @@ done 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" +# ============================================================================================== +# ── Storage mode (internal NVMe/SSD vs USB flash boot) ─────────────────────────────────────── +# ============================================================================================== + +boot_src=$(findmnt -n -o SOURCE /boot 2>/dev/null) +if [[ -n "$boot_src" ]]; then + boot_dev=$(lsblk -no PKNAME "$boot_src" 2>/dev/null | head -1) + if [[ "$boot_dev" =~ ^nvme ]]; then + storage_mode=true + else + boot_transport=$(cat "/sys/block/${boot_dev}/device/transport" 2>/dev/null) + [[ "$boot_transport" == "usb" ]] && storage_mode=false || storage_mode=true + fi + _set_conf_var "${MY_ID}_STORAGE_MODE_INTERNAL" "$storage_mode" \ + "Storage mode (NVMe/SSD=true, USB=false)" +fi + +# ============================================================================================== +# ── Master conf: host identity + Gitea container ───────────────────────────────────────────── +# ============================================================================================== + +if [[ -f "$MASTER_CONF" ]]; then + echo "" + echo " ── Master conf ──" + _set_conf_var "$MY_ID" "$LOCAL_SERVER_NAME" "master.conf ${MY_ID} hostname" "$MASTER_CONF" + + gitea_container=$(docker ps -a --format '{{.Names}}' 2>/dev/null | grep -im1 "gitea") + [[ -n "$gitea_container" ]] && \ + _set_conf_var "GITEA_CONTAINER" "$gitea_container" "Gitea container" "$MASTER_CONF" +fi + # ============================================================================================== # ── Summary + push ──────────────────────────────────────────────────────────────────────────── # ==============================================================================================