1	#!/bin/bash
2	# ==============================================================================================
3	# ============================= Conf Auto-Populate =============================================
4	# ==============================================================================================
5	#
6	# PURPOSE
7	# ─────────────────────────────────────────────────────────────────────────────
8	# Reads credentials and settings from locally running services and writes
9	# them into the local host conf. Safe to run multiple times — only populates
10	# EMPTY fields, never overwrites existing values unless --overwrite is passed.
11	#
12	# After populating, pushes the updated conf to all partners via conf_sync.sh
13	# so they have the fresh keys in their /tmp/.vv/ cache immediately.
14	#
15	# ==============================================================================================
16	# AUTO-DETECTED FIELDS
17	# ==============================================================================================
18	#
19	#   HOSTN_RADARR_API_KEY      from Radarr config.xml (found via docker volume mount)
20	#   HOSTN_SONARR_API_KEY      from Sonarr config.xml
21	#   HOSTN_LIDARR_API_KEY      from Lidarr config.xml
22	#   HOSTN_SLSKD_API_KEY       from slskd config.yml
23	#   HOSTN_SABNZBD_API_KEY     from sabnzbd.ini
24	#   HOSTN_EMBY_CONTAINER      fuzzy match from docker ps
25	#   HOSTN_JELLYFIN_CONTAINER  fuzzy match from docker ps
26	#   HOSTN_RADARR_MOVIE_ROOT   from Radarr rootFolder API
27	#   HOSTN_SONARR_TV_ROOT      from Sonarr rootFolder API
28	#   HOSTN_LIDARR_MUSIC_ROOT   from Lidarr rootFolder API
29	#   HOSTN_SYS_WATCHDOG_NIC    from ip route default gateway interface
30	#
31	# ==============================================================================================
32	# RUNTIME MODES
33	# ==============================================================================================
34	#
35	#   conf_populate.sh              Populate empty fields only
36	#   conf_populate.sh --overwrite  Overwrite all detected fields (re-sync after arr key rotation)
37	#   conf_populate.sh --dry-run    Show what would be written, no changes
38	#   conf_populate.sh --log        Verbose output
39	#   conf_populate.sh --no-push    Skip pushing to partners after update
40	#
41	# ==============================================================================================
42
43	SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
44	source "$SCRIPT_DIR/../../../load_config.sh"
45	SCRIPTS_ROOT="$SCRIPTS_DIR"
46
47	OVERWRITE=false
48	NO_PUSH=false
49	FILTERED_ARGS=()
50	for arg in "$@"; do
51	    case "$arg" in
52	        --overwrite) OVERWRITE=true ;;
53	        --no-push)   NO_PUSH=true ;;
54	        *)           FILTERED_ARGS+=("$arg") ;;
55	    esac
56	done
57	parse_args "${FILTERED_ARGS[@]}"
58	detect_hosts
59
60	[[ "$EUID" -ne 0 ]] && { error "Must be run as root"; exit 1; }
61
62	CONF_FILE="$SCRIPTS_ROOT/Configurations/${MY_ID,,}.conf"
63	[[ ! -f "$CONF_FILE" ]] && { error "Conf file not found: $CONF_FILE"; exit 1; }
64
65	log "$ICON_GEAR Config: conf=${CONF_FILE} overwrite=${OVERWRITE:-false} no-push=${NO_PUSH:-false}"
66
67	UPDATED=0
68	SKIPPED=0
69
70	echo ""
71	echo "━━━ $ICON_GEAR Conf Auto-Populate — $MY_ID ($LOCAL_SERVER_NAME) ━━━"
72	echo ""
73	[[ "$DRY_RUN"   == true ]] && warn "DRY RUN — no changes will be made"
74	[[ "$OVERWRITE" == true ]] && warn "OVERWRITE mode — existing values will be replaced"
75
76	# ── Helper: write a var into conf if empty (or --overwrite) ──────────────────
77	_set_conf_var() {
78	    local var_name="$1" value="$2" label="$3"
79	    [[ -z "$value" ]] && return
80
81	    # Check current value in conf
82	    local current
83	    current=$(grep -oP "(?<=^\s*${var_name}=\")[^\"]*" "$CONF_FILE" 2>/dev/null | head -1)
84
85	    if [[ -n "$current" ]] && [[ "$OVERWRITE" == false ]]; then
86	        log "$label: already set (${current:0:8}…) — skipping"
87	        (( SKIPPED++ ))
88	        return
89	    fi
90
91	    if [[ "$DRY_RUN" == true ]]; then
92	        warn "DRY RUN — would set $var_name = ${value:0:8}…"
93	        return
94	    fi
95
96	    # Update or append the var line
97	    if grep -q "^\s*${var_name}=" "$CONF_FILE"; then
98	        sed -i "s|^\(\s*${var_name}\s*=\s*\)\"[^\"]*\"|\1\"${value}\"|" "$CONF_FILE"
99	    else
100	        printf '\n    %s="%s"\n' "$var_name" "$value" >> "$CONF_FILE"
101	    fi
102	    info "$label: set ✅"
103	    (( UPDATED++ ))
104	}
105
106	# ── Helper: find arr config dir via docker volume mount ───────────────────────
107	# Looks for a container matching the pattern, then reads its /config volume path.
108	# Falls back to DOCKER_APPDATA_BASE/<ContainerName> if volume not found.
109	_arr_config_dir() {
110	    local pattern="$1"
111	    local container_name
112	    container_name=$(docker ps -a --format '{{.Names}}' 2>/dev/null | \
113	        grep -im1 "^${pattern}")
114	    [[ -z "$container_name" ]] && return 1
115
116	    local config_path
117	    config_path=$(docker inspect "$container_name" 2>/dev/null | \
118	        jq -r '.[0].Mounts[]? | select(.Destination == "/config") | .Source' 2>/dev/null | head -1)
119	    [[ -z "$config_path" ]] && config_path="${DOCKER_APPDATA_BASE:-/mnt/user/appdata}/${container_name}"
120
121	    [[ -d "$config_path" ]] && echo "$config_path" || return 1
122	}
123
124	# ── Helper: read XML tag value ────────────────────────────────────────────────
125	_xml_val() {
126	    local file="$1" tag="$2"
127	    grep -oP "(?<=<${tag}>)[^<]+" "$file" 2>/dev/null | head -1
128	}
129
130	# ==============================================================================================
131	# ── Arr API keys + root paths ─────────────────────────────────────────────────────────────────
132	# ==============================================================================================
133
134	for arr in radarr sonarr lidarr; do
135	    arr_upper="${arr^^}"
136	    config_dir=$(_arr_config_dir "$arr") || {
137	        log "${arr_upper}: no running container found — skipping"
138	        continue
139	    }
140	    config_xml="${config_dir}/config.xml"
141
142	    if [[ ! -f "$config_xml" ]]; then
143	        log "${arr_upper}: config.xml not found at $config_xml — skipping"
144	        continue
145	    fi
146
147	    key=$(_xml_val "$config_xml" "ApiKey")
148	    port=$(_xml_val "$config_xml" "Port")
149	    url_base="http://localhost:${port:-$(case $arr in radarr) echo 7878;; sonarr) echo 8989;; lidarr) echo 8686;; esac)}"
150
151	    _set_conf_var "${MY_ID}_${arr_upper}_API_KEY" "$key" "${arr_upper} API key"
152
153	    # Root paths from arr's own rootFolder API
154	    if [[ -n "$key" ]]; then
155	        case "$arr" in lidarr) api_ver="v1" ;; *) api_ver="v3" ;; esac
156	        root_json=$(curl -sf --max-time 5 \
157	            -H "X-Api-Key: $key" "${url_base}/api/${api_ver}/rootfolder" 2>/dev/null)
158	        root_path=$(echo "$root_json" | jq -r '.[0].path // empty' 2>/dev/null)
159
160	        case "$arr" in
161	            radarr) _set_conf_var "${MY_ID}_RADARR_MOVIE_ROOT" "$root_path" "Radarr movie root" ;;
162	            sonarr) _set_conf_var "${MY_ID}_SONARR_TV_ROOT"    "$root_path" "Sonarr TV root" ;;
163	            lidarr) _set_conf_var "${MY_ID}_LIDARR_MUSIC_ROOT" "$root_path" "Lidarr music root" ;;
164	        esac
165	    fi
166	done
167
168	# ==============================================================================================
169	# ── SABnzbd API key ───────────────────────────────────────────────────────────────────────────
170	# ==============================================================================================
171
172	sab_dir=$(_arr_config_dir "sabnzbd") && {
173	    sab_ini=$(find "$sab_dir" -maxdepth 2 -name "sabnzbd.ini" 2>/dev/null | head -1)
174	    if [[ -f "$sab_ini" ]]; then
175	        sab_key=$(grep -oP '(?<=^api_key\s*=\s*)\S+' "$sab_ini" 2>/dev/null | head -1)
176	        _set_conf_var "${MY_ID}_SABNZBD_API_KEY" "$sab_key" "SABnzbd API key"
177	    fi
178	}
179
180	# ==============================================================================================
181	# ── slskd API key ─────────────────────────────────────────────────────────────────────────────
182	# ==============================================================================================
183
184	slskd_dir=$(_arr_config_dir "slskd") && {
185	    slskd_yml=$(find "$slskd_dir" -maxdepth 2 -name "*.yml" -o -name "*.yaml" 2>/dev/null | head -1)
186	    if [[ -f "$slskd_yml" ]]; then
187	        slskd_key=$(grep -oP '(?<=api_key:\s)[\w-]+' "$slskd_yml" 2>/dev/null | head -1)
188	        [[ -z "$slskd_key" ]] && \
189	            slskd_key=$(grep -oP '(?<=apikey:\s)[\w-]+' "$slskd_yml" 2>/dev/null | head -1)
190	        _set_conf_var "${MY_ID}_SLSKD_API_KEY" "$slskd_key" "slskd API key"
191	    fi
192	}
193
194	# ==============================================================================================
195	# ── Container names ───────────────────────────────────────────────────────────────────────────
196	# ==============================================================================================
197
198	for pattern in "emby" "jellyfin"; do
199	    container=$(docker ps -a --format '{{.Names}}' 2>/dev/null | grep -im1 "^${pattern}")
200	    [[ -z "$container" ]] && continue
201	    case "$pattern" in
202	        emby)     _set_conf_var "${MY_ID}_EMBY_CONTAINER"     "$container" "Emby container name" ;;
203	        jellyfin) _set_conf_var "${MY_ID}_JELLYFIN_CONTAINER" "$container" "Jellyfin container name" ;;
204	    esac
205	done
206
207	# ==============================================================================================
208	# ── Network interface ─────────────────────────────────────────────────────────────────────────
209	# ==============================================================================================
210
211	nic=$(ip route show default 2>/dev/null | grep -oP '(?<=dev )\S+' | head -1)
212	_set_conf_var "${MY_ID}_SYS_WATCHDOG_NIC" "$nic" "Default NIC"
213
214	# ==============================================================================================
215	# ── Summary + push ────────────────────────────────────────────────────────────────────────────
216	# ==============================================================================================
217
218	echo ""
219	echo "━━━━━ $ICON_SUMMARY Conf Populate Summary ━━━━━"
220	echo "  Updated: $UPDATED field(s)"
221	echo "  Skipped: $SKIPPED already set"
222	echo "  Conf:    $CONF_FILE"
223	echo "━━━━━━━━━━━━━━━━━━━━━"
224
225	if [[ "$UPDATED" -gt 0 ]] && [[ "$DRY_RUN" == false ]] && [[ "$NO_PUSH" == false ]]; then
226	    echo ""
227	    info "Pushing updated conf to partners..."
228	    bash "$SCRIPTS_ROOT/System_Essentials/conf_sync.sh" --push-only "${EXTRA_FLAGS[@]}" || true
229	fi
230

#!/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"
SCRIPTS_ROOT="$SCRIPTS_DIR"

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; }

log "$ICON_GEAR Config: conf=${CONF_FILE} overwrite=${OVERWRITE:-false} no-push=${NO_PUSH:-false}"

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/<ContainerName> 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
        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


Bash

grep -E "^\s+HOST[N1]_" /boot/config/plugins/varaverk/Configurations/host.conf.template 2>/dev/null || grep -E "HOSTN_|HOST1_" /boot/config/plugins/varaverk/Configurations/host.conf.template | head -80

List all variables in host.conf template


    HOSTN_STORAGE_MODE_INTERNAL=true
    HOSTN_SSH_KEY=""    # e.g. /root/.ssh/myserver_rsync_automation
    HOSTN_OWNER=""      # short identifier for this server (e.g. myserver)
    HOSTN_OWNER_EMAIL=""
    HOSTN_UNRAID_API_KEY=""
    HOSTN_EMBY_CONTAINER="Emby"
    HOSTN_EMBY_URL="http://localhost:8096"
    HOSTN_EMBY_API_KEY=""   # Emby Dashboard → API Keys → + New Key
    HOSTN_JELLYFIN_CONTAINER="Jellyfin"
    HOSTN_JELLYFIN_URL="http://localhost:8095"
    HOSTN_JELLYFIN_API_KEY=""   # Jellyfin Dashboard → Administration → API Keys
    HOSTN_GITEA_API_TOKEN=""
    HOSTN_DISCORD_WEBHOOK=""
    HOSTN_PARTNERSHIP_AUTH_WEBUIS=(
    HOSTN_PARTNERSHIP_AUTH_STACK=(
    HOSTN_PARTNERSHIP_ARR_STACK=(
    HOSTN_PARTNERSHIP_MIRROR_BACKUPS=(
    HOSTN_PARTNERSHIP_OWN_CONTAINERS=(
    HOSTN_PARTNERSHIP_PROVISION_EMBY_ADMIN=false
    HOSTN_PARTNERSHIP_EMBY_PORT=8096
    HOSTN_PARTNERSHIP_EMBY_ADMIN_USER=""
    HOSTN_PARTNERSHIP_EMBY_ADMIN_PASS=""
    HOSTN_DAILY_SYNC_SHARES=(
    HOSTN_WEEKLY_SYNC_SHARES=(
    HOSTN_INTERMEDIATE_SYNC_SHARES=(
    HOSTN_CRITICAL_SYNC_SHARES=(
    HOSTN_BACKUP_VERIFY_SHARES=(
    HOSTN_DAILY_RESTART_CONTAINERS=(
    HOSTN_WEEKLY_RESTART_CONTAINERS=(
    HOSTN_WATCHDOG_REQUIRED_CONTAINERS=(
    HOSTN_WATCHDOG_SCAN_IGNORE=(
    HOSTN_NETWORK_WATCHDOG_DDNS_DOMAIN=""       # e.g. myserver.com
    HOSTN_NETWORK_WATCHDOG_DDNS_CONTAINER=""    # e.g. MyServer.com
    HOSTN_NETWORK_WATCHDOG_NPM_URL=""           # e.g. https://myserver.com
    HOSTN_NETWORK_CONNECT_CONTAINERS=(
    HOSTN_NETWORK_CONNECT_NETWORKS=(
    HOSTN_DDNS_CONTAINERS=(
    HOSTN_TIER1_WRITEBACK_DELAY=60      # skip Emby writeback if outage under 1hr
    HOSTN_MEDIA_PERMISSION_SHARES=(
    HOSTN_ANIME_CLEAN_FOLDERS=(
    HOSTN_MEDIA_CLEAN_FOLDERS=(
    HOSTN_CERT_MONITOR_DOMAINS=(
    HOSTN_SMART_IGNORE_DRIVES=(
    HOSTN_ZFS_REPORT_IGNORE_POOLS=(
    HOSTN_RAMDISK_SIZE="10G"
    HOSTN_RAMDISK_WARN_GB=8.5
    HOSTN_RAMDISK_LOW_GB=7
    HOSTN_TRANSCODE_SSD="/mnt/cache/Temp_Storage/Emby/Transcodes/"
    HOSTN_TRANSCODE_SERVERS=(
    HOSTN_SLSKD_URL="http://localhost:8980"
    HOSTN_SLSKD_API_KEY=""
    HOSTN_SLSKD_FAILED_IMPORTS_DIR=""
    HOSTN_SABNZBD_URL="http://localhost:8180"
    HOSTN_SABNZBD_API_KEY=""
    HOSTN_QBIT_URL="http://localhost:8080"
    HOSTN_QBIT_USERNAME="admin"
    HOSTN_QBIT_PASSWORD=""
    HOSTN_LIDARR_URL="http://localhost:8686"
    HOSTN_LIDARR_API_KEY=""
    HOSTN_LIDARR_MUSIC_ROOT="/mnt/user/Music"
    HOSTN_FANART_API_KEY=""
    HOSTN_LASTFM_API_KEY=""
    HOSTN_SONARR_URL="http://localhost:8989"
    HOSTN_SONARR_API_KEY=""
    HOSTN_SONARR_TV_ROOT="/mnt/user/Tv_Shows"
    HOSTN_RADARR_URL="http://localhost:7878"
    HOSTN_RADARR_API_KEY=""
    HOSTN_RADARR_MOVIE_ROOT="/mnt/user/Movies"
    HOSTN_LIDARR_RECOVERY=false
    HOSTN_SONARR_RECOVERY=true
    HOSTN_RADARR_RECOVERY=true
    HOSTN_SYS_WATCHDOG_CHECK_DOCKER=true
    HOSTN_SYS_WATCHDOG_CHECK_ROOTFS=true
    HOSTN_SYS_WATCHDOG_CHECK_RAM=true
    HOSTN_SYS_WATCHDOG_CHECK_LOAD=true
    HOSTN_SYS_WATCHDOG_CHECK_TEMP=true
    HOSTN_SYS_WATCHDOG_CHECK_ZOMBIES=true
    HOSTN_SYS_WATCHDOG_CHECK_LOG=true
    HOSTN_SYS_WATCHDOG_CHECK_TMP=true
    HOSTN_SYS_WATCHDOG_CHECK_FD=true
    HOSTN_SYS_WATCHDOG_CHECK_RUNAWAY=true
    HOSTN_SYS_WATCHDOG_NIC=""   # e.g. eth0 — for network monitoring
    HOSTN_RW_PAUSE_CONTAINERS=(
    HOSTN_RW_STOP_CONTAINERS=(


Here's what's in the template but not yet populated, broken into what's realistic:

**Easy / high confidence:**
- `HOSTN_OWNER` — strip `unRAID-` from hostname, lowercase. Already doing this implicitly for SSH key naming, just never writing it to conf
- Arr service **URLs** — we already read the port from `config.xml` for each arr, just never construct and write `http://localhost:PORT`
- `HOSTN_SABNZBD_URL` — sabnzbd.ini has the port too
- `HOSTN_SLSKD_URL` — same, in the yml config

**Moderate / worth it:**
- `HOSTN_QBIT_URL`, `HOSTN_QBIT_USERNAME`, `HOSTN_QBIT_PASSWORD` — qBittorrent stores plaintext creds in `qBittorrent.conf` (`WebUI\Username`, `WebUI\Password`, `WebUI\Port`)
- `HOSTN_EMBY_URL` / `HOSTN_JELLYFIN_URL` — we have the container name, can `docker inspect` for the host port binding rather than assuming default
- `HOSTN_TRANSCODE_SSD` — `docker inspect` on the Emby/Jellyfin container, look for a volume mounted to `/transcode` or `/config/transcodes`, use the host path

**Probably not worth it:**
- `HOSTN_EMBY_API_KEY` — stored in an SQLite auth DB inside the container; readable but fragile, and user-issued keys feel intentional
- `HOSTN_GITEA_API_TOKEN` — no config file to pull from; tokens are user-generated
- `HOSTN_DISCORD_WEBHOOK`, `HOSTN_OWNER_EMAIL` — inherently manual
- Partnership arrays, sync shares — all user-defined topology, can't derive from running services

The biggest wins would be `HOSTN_OWNER` (trivial one-liner) and the service URLs (we already have ports in hand for arrs and SABnzbd, just not writing them). qBittorrent creds are a nice surprise since they're fully plaintext in the conf file. Want me to add all of those?


yeah add all of those


Reading the current file once more to get exact content for the rewrite.


Wasted call — file unchanged since your last Read. Refer to that earlier tool_result instead.


Good, I have it. Changes are spread throughout every section so I'll rewrite the whole file cleanly:


Write

#!/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_OWNER               from hostname (strip unRAID- prefix, lowercase)
#   HOSTN_RADARR_API_KEY      from Radarr config.xml (found via docker volume mount)
#   HOSTN_RADARR_URL          from Radarr config.xml port
#   HOSTN_SONARR_API_KEY      from Sonarr config.xml
#   HOSTN_SONARR_URL          from Sonarr config.xml port
#   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_SABNZBD_API_KEY     from sabnzbd.ini
#   HOSTN_SABNZBD_URL         from sabnzbd.ini port
#   HOSTN_SLSKD_API_KEY       from slskd config.yml
#   HOSTN_SLSKD_URL           from slskd config.yml port
#   HOSTN_QBIT_URL            from qBittorrent.conf WebUI port
#   HOSTN_QBIT_USERNAME       from qBittorrent.conf WebUI username
#   HOSTN_QBIT_PASSWORD       from qBittorrent.conf WebUI password (plaintext only)
#   HOSTN_EMBY_CONTAINER      fuzzy match from docker ps
#   HOSTN_EMBY_URL            from docker port binding
#   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
#
# ==============================================================================================
# 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"
SCRIPTS_ROOT="$SCRIPTS_DIR"

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; }

log "$ICON_GEAR Config: conf=${CONF_FILE} overwrite=${OVERWRITE:-false} no-push=${NO_PUSH:-false}"

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

    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

    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 ───────────────────────
_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
}

# ── Helper: get host-side port for a container's internal port ────────────────
_docker_host_port() {
    local container="$1" container_port="$2"
    docker inspect "$container" 2>/dev/null | \
        jq -r --arg p "${container_port}/tcp" \
        '.[0].NetworkSettings.Ports[$p]?[0].HostPort // empty' 2>/dev/null | head -1
}

# ── Helper: get host path for a container destination mount ──────────────────
_docker_volume_host() {
    local container="$1" dest="$2"
    docker inspect "$container" 2>/dev/null | \
        jq -r --arg d "$dest" \
        '.[0].Mounts[]? | select(.Destination == $d) | .Source' 2>/dev/null | head -1
}

# ==============================================================================================
# ── Owner short name ──────────────────────────────────────────────────────────────────────────
# ==============================================================================================

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"

# ==============================================================================================
# ── Arr API keys + URLs + 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")
    port="${port:-$(case $arr in radarr) echo 7878;; sonarr) echo 8989;; lidarr) echo 8686;; esac)}"
    url_base="http://localhost:${port}"

    _set_conf_var "${MY_ID}_${arr_upper}_API_KEY" "$key"      "${arr_upper} API key"
    _set_conf_var "${MY_ID}_${arr_upper}_URL"     "$url_base" "${arr_upper} URL"

    if [[ -n "$key" ]]; then
        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 + URL ─────────────────────────────────────────────────────────────────────
# ==============================================================================================

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)
        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_URL"     "http://localhost:${sab_port:-8080}" "SABnzbd URL"
    fi
}

# ==============================================================================================
# ── slskd API key + URL ───────────────────────────────────────────────────────────────────────
# ==============================================================================================

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)
        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_URL"     "http://localhost:${slskd_port:-5030}" "slskd URL"
    fi
}

# ==============================================================================================
# ── qBittorrent URL + credentials ────────────────────────────────────────────────────────────
# ==============================================================================================

qbit_dir=$(_arr_config_dir "qbittorrent") && {
    qbit_conf=$(find "$qbit_dir" -maxdepth 3 -name "qBittorrent.conf" 2>/dev/null | head -1)
    if [[ -f "$qbit_conf" ]]; then
        qbit_port=$(grep -oP '(?<=WebUI\\Port=)\d+'    "$qbit_conf" 2>/dev/null | head -1)
        qbit_user=$(grep -oP '(?<=WebUI\\Username=)\S+' "$qbit_conf" 2>/dev/null | head -1)
        # Only capture plaintext password — PBKDF2 hashes are not usable
        qbit_pass=$(grep -oP '(?<=WebUI\\Password=)[^\r\n]+' "$qbit_conf" 2>/dev/null | \
            grep -v '@ByteArray' | head -1)
        _set_conf_var "${MY_ID}_QBIT_URL"      "http://localhost:${qbit_port:-8080}" "qBittorrent URL"
        _set_conf_var "${MY_ID}_QBIT_USERNAME" "$qbit_user"                          "qBittorrent username"
        _set_conf_var "${MY_ID}_QBIT_PASSWORD" "$qbit_pass"                          "qBittorrent password"
    fi
}

# ==============================================================================================
# ── Media server container names + URLs + transcode path ─────────────────────────────────────
# ==============================================================================================

transcode_dir=""

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)
            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"
            ;;
        jellyfin)
            host_port=$(_docker_host_port "$container" "8096")
            _set_conf_var "${MY_ID}_JELLYFIN_CONTAINER" "$container"                          "Jellyfin container name"
            _set_conf_var "${MY_ID}_JELLYFIN_URL"       "http://localhost:${host_port:-8095}" "Jellyfin URL"
            ;;
    esac

    # Transcode path: first container with a /transcode mount wins
    if [[ -z "$transcode_dir" ]]; then
        transcode_dir=$(_docker_volume_host "$container" "/transcode")
    fi
done

[[ -n "$transcode_dir" ]] && \
    _set_conf_var "${MY_ID}_TRANSCODE_SSD" "${transcode_dir%/}/" "Transcode SSD path"

# ==============================================================================================
# ── 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


The file /boot/config/plugins/varaverk/Plugin/unraid/Tools/conf_populate.sh has been updated successfully. (file state is current in your context — no need to Read it back)

#!/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_OWNER               from hostname (strip unRAID- prefix, lowercase)
#   HOSTN_RADARR_API_KEY      from Radarr config.xml (found via docker volume mount)
#   HOSTN_RADARR_URL          from Radarr config.xml port
#   HOSTN_SONARR_API_KEY      from Sonarr config.xml
#   HOSTN_SONARR_URL          from Sonarr config.xml port
#   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_SABNZBD_API_KEY     from sabnzbd.ini
#   HOSTN_SABNZBD_URL         from sabnzbd.ini port
#   HOSTN_SLSKD_API_KEY       from slskd config.yml
#   HOSTN_SLSKD_URL           from slskd config.yml port
#   HOSTN_QBIT_URL            from qBittorrent.conf WebUI port
#   HOSTN_QBIT_USERNAME       from qBittorrent.conf WebUI username
#   HOSTN_QBIT_PASSWORD       from qBittorrent.conf WebUI password (plaintext only)
#   HOSTN_EMBY_CONTAINER      fuzzy match from docker ps
#   HOSTN_EMBY_URL            from docker port binding
#   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
#
# ==============================================================================================
# 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"
SCRIPTS_ROOT="$SCRIPTS_DIR"

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; }

log "$ICON_GEAR Config: conf=${CONF_FILE} overwrite=${OVERWRITE:-false} no-push=${NO_PUSH:-false}"

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

    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

    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 ───────────────────────
_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
}

# ── Helper: get host-side port for a container's internal port ────────────────
_docker_host_port() {
    local container="$1" container_port="$2"
    docker inspect "$container" 2>/dev/null | \
        jq -r --arg p "${container_port}/tcp" \
        '.[0].NetworkSettings.Ports[$p]?[0].HostPort // empty' 2>/dev/null | head -1
}

# ── Helper: get host path for a container destination mount ──────────────────
_docker_volume_host() {
    local container="$1" dest="$2"
    docker inspect "$container" 2>/dev/null | \
        jq -r --arg d "$dest" \
        '.[0].Mounts[]? | select(.Destination == $d) | .Source' 2>/dev/null | head -1
}

# ==============================================================================================
# ── Owner short name ──────────────────────────────────────────────────────────────────────────
# ==============================================================================================

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"

# ==============================================================================================
# ── Arr API keys + URLs + 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")
    port="${port:-$(case $arr in radarr) echo 7878;; sonarr) echo 8989;; lidarr) echo 8686;; esac)}"
    url_base="http://localhost:${port}"

    _set_conf_var "${MY_ID}_${arr_upper}_API_KEY" "$key"      "${arr_upper} API key"
    _set_conf_var "${MY_ID}_${arr_upper}_URL"     "$url_base" "${arr_upper} URL"

    if [[ -n "$key" ]]; then
        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 + URL ─────────────────────────────────────────────────────────────────────
# ==============================================================================================

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)
        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_URL"     "http://localhost:${sab_port:-8080}" "SABnzbd URL"
    fi
}

# ==============================================================================================
# ── slskd API key + URL ───────────────────────────────────────────────────────────────────────
# ==============================================================================================

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)
        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_URL"     "http://localhost:${slskd_port:-5030}" "slskd URL"
    fi
}

# ==============================================================================================
# ── qBittorrent URL + credentials ────────────────────────────────────────────────────────────
# ==============================================================================================

qbit_dir=$(_arr_config_dir "qbittorrent") && {
    qbit_conf=$(find "$qbit_dir" -maxdepth 3 -name "qBittorrent.conf" 2>/dev/null | head -1)
    if [[ -f "$qbit_conf" ]]; then
        qbit_port=$(grep -oP '(?<=WebUI\\Port=)\d+'    "$qbit_conf" 2>/dev/null | head -1)
        qbit_user=$(grep -oP '(?<=WebUI\\Username=)\S+' "$qbit_conf" 2>/dev/null | head -1)
        # Only capture plaintext password — PBKDF2 hashes are not usable
        qbit_pass=$(grep -oP '(?<=WebUI\\Password=)[^\r\n]+' "$qbit_conf" 2>/dev/null | \
            grep -v '@ByteArray' | head -1)
        _set_conf_var "${MY_ID}_QBIT_URL"      "http://localhost:${qbit_port:-8080}" "qBittorrent URL"
        _set_conf_var "${MY_ID}_QBIT_USERNAME" "$qbit_user"                          "qBittorrent username"
        _set_conf_var "${MY_ID}_QBIT_PASSWORD" "$qbit_pass"                          "qBittorrent password"
    fi
}

# ==============================================================================================
# ── Media server container names + URLs + transcode path ─────────────────────────────────────
# ==============================================================================================

transcode_dir=""

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)
            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"
            ;;
        jellyfin)
            host_port=$(_docker_host_port "$container" "8096")
            _set_conf_var "${MY_ID}_JELLYFIN_CONTAINER" "$container"                          "Jellyfin container name"
            _set_conf_var "${MY_ID}_JELLYFIN_URL"       "http://localhost:${host_port:-8095}" "Jellyfin URL"
            ;;
    esac

    # Transcode path: first container with a /transcode mount wins
    if [[ -z "$transcode_dir" ]]; then
        transcode_dir=$(_docker_volume_host "$container" "/transcode")
    fi
done

[[ -n "$transcode_dir" ]] && \
    _set_conf_var "${MY_ID}_TRANSCODE_SSD" "${transcode_dir%/}/" "Transcode SSD path"

# ==============================================================================================
# ── 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

+#   HOSTN_OWNER               from hostname (strip unRAID- prefix, lowercase)

+#   HOSTN_RADARR_URL          from Radarr config.xml port

#   HOSTN_SONARR_API_KEY      from Sonarr config.xml

+#   HOSTN_SONARR_URL          from Sonarr config.xml port

#   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_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_SYS_WATCHDOG_NIC    from ip route default gateway interface

+#   HOSTN_SABNZBD_API_KEY     from sabnzbd.ini

+#   HOSTN_SABNZBD_URL         from sabnzbd.ini port

+#   HOSTN_SLSKD_API_KEY       from slskd config.yml

+#   HOSTN_SLSKD_URL           from slskd config.yml port

+#   HOSTN_QBIT_URL            from qBittorrent.conf WebUI port

+#   HOSTN_QBIT_USERNAME       from qBittorrent.conf WebUI username

+#   HOSTN_QBIT_PASSWORD       from qBittorrent.conf WebUI password (plaintext only)

+#   HOSTN_EMBY_CONTAINER      fuzzy match from docker ps

+#   HOSTN_EMBY_URL            from docker port binding

+#   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

local var_name="$1" value="$2" label="$3"

if grep -q "^\s*${var_name}=" "$CONF_FILE"; then

-# Looks for a container matching the pattern, then reads its /config volume path.

-# Falls back to DOCKER_APPDATA_BASE/<ContainerName> if volume not found.

-    container_name=$(docker ps -a --format '{{.Names}}' 2>/dev/null | \

+    container_name=$(docker ps -a --format '{{.Names}}' 2>/dev/null | grep -im1 "^${pattern}")

+# ── Helper: get host-side port for a container's internal port ────────────────

+    local container="$1" container_port="$2"

+    docker inspect "$container" 2>/dev/null | \

+        jq -r --arg p "${container_port}/tcp" \

+        '.[0].NetworkSettings.Ports[$p]?[0].HostPort // empty' 2>/dev/null | head -1

+# ── Helper: get host path for a container destination mount ──────────────────

+        '.[0].Mounts[]? | select(.Destination == $d) | .Source' 2>/dev/null | head -1

-# ── Arr API keys + root paths ─────────────────────────────────────────────────────────────────

+# ── Owner short name ──────────────────────────────────────────────────────────────────────────

+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"

+# ── Arr API keys + URLs + root paths ─────────────────────────────────────────────────────────

config_dir=$(_arr_config_dir "$arr") || {

-    url_base="http://localhost:${port:-$(case $arr in radarr) echo 7878;; sonarr) echo 8989;; lidarr) echo 8686;; esac)}"

+    port="${port:-$(case $arr in radarr) echo 7878;; sonarr) echo 8989;; lidarr) echo 8686;; esac)}"

+    url_base="http://localhost:${port}"

-    _set_conf_var "${MY_ID}_${arr_upper}_API_KEY" "$key" "${arr_upper} API key"

+    _set_conf_var "${MY_ID}_${arr_upper}_API_KEY" "$key"      "${arr_upper} API key"

+    _set_conf_var "${MY_ID}_${arr_upper}_URL"     "$url_base" "${arr_upper} URL"

-    # Root paths from arr's own rootFolder API

case "$arr" in lidarr) api_ver="v1" ;; *) api_ver="v3" ;; esac

-            sonarr) _set_conf_var "${MY_ID}_SONARR_TV_ROOT"    "$root_path" "Sonarr TV root" ;;

+            sonarr) _set_conf_var "${MY_ID}_SONARR_TV_ROOT"    "$root_path" "Sonarr TV root"    ;;

-# ── SABnzbd API key ───────────────────────────────────────────────────────────────────────────

+# ── SABnzbd API key + URL ─────────────────────────────────────────────────────────────────────

sab_dir=$(_arr_config_dir "sabnzbd") && {

-        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"

+        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_URL"     "http://localhost:${sab_port:-8080}" "SABnzbd URL"

-# ── slskd API key ─────────────────────────────────────────────────────────────────────────────

+# ── slskd API key + URL ───────────────────────────────────────────────────────────────────────

slskd_dir=$(_arr_config_dir "slskd") && {

-    slskd_yml=$(find "$slskd_dir" -maxdepth 2 -name "*.yml" -o -name "*.yaml" 2>/dev/null | head -1)

+    slskd_yml=$(find "$slskd_dir" -maxdepth 2 \( -name "*.yml" -o -name "*.yaml" \) 2>/dev/null | head -1)

-        slskd_key=$(grep -oP '(?<=api_key:\s)[\w-]+' "$slskd_yml" 2>/dev/null | head -1)

+        slskd_key=$(grep -oP '(?<=api_key:\s)[\w-]+'  "$slskd_yml" 2>/dev/null | head -1)

-        _set_conf_var "${MY_ID}_SLSKD_API_KEY" "$slskd_key" "slskd API key"

+        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_URL"     "http://localhost:${slskd_port:-5030}" "slskd URL"

-# ── Container names ───────────────────────────────────────────────────────────────────────────

+# ── qBittorrent URL + credentials ────────────────────────────────────────────────────────────

+qbit_dir=$(_arr_config_dir "qbittorrent") && {

+    qbit_conf=$(find "$qbit_dir" -maxdepth 3 -name "qBittorrent.conf" 2>/dev/null | head -1)

+        qbit_port=$(grep -oP '(?<=WebUI\\Port=)\d+'    "$qbit_conf" 2>/dev/null | head -1)

+        qbit_user=$(grep -oP '(?<=WebUI\\Username=)\S+' "$qbit_conf" 2>/dev/null | head -1)

+        # Only capture plaintext password — PBKDF2 hashes are not usable

+        qbit_pass=$(grep -oP '(?<=WebUI\\Password=)[^\r\n]+' "$qbit_conf" 2>/dev/null | \

+            grep -v '@ByteArray' | head -1)

+        _set_conf_var "${MY_ID}_QBIT_URL"      "http://localhost:${qbit_port:-8080}" "qBittorrent URL"

+        _set_conf_var "${MY_ID}_QBIT_USERNAME" "$qbit_user"                          "qBittorrent username"

+        _set_conf_var "${MY_ID}_QBIT_PASSWORD" "$qbit_pass"                          "qBittorrent password"

+# ── Media server container names + URLs + transcode path ─────────────────────────────────────

-        emby)     _set_conf_var "${MY_ID}_EMBY_CONTAINER"     "$container" "Emby container name" ;;

-        jellyfin) _set_conf_var "${MY_ID}_JELLYFIN_CONTAINER" "$container" "Jellyfin container name" ;;

+            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}_JELLYFIN_CONTAINER" "$container"                          "Jellyfin container name"

+            _set_conf_var "${MY_ID}_JELLYFIN_URL"       "http://localhost:${host_port:-8095}" "Jellyfin URL"

+    # Transcode path: first container with a /transcode mount wins

+        transcode_dir=$(_docker_volume_host "$container" "/transcode")

+    _set_conf_var "${MY_ID}_TRANSCODE_SSD" "${transcode_dir%/}/" "Transcode SSD path"


Bash

bash /boot/config/plugins/varaverk/Plugin/unraid/Tools/conf_populate.sh --dry-run 2>&1

Dry run with all new detections


━━━ ⚙ Conf Auto-Populate — HOST1 (unRAID-Gmer4Lfe) ━━━

⚠  [WARN]  DRY RUN — no changes will be made
⚠  [WARN]  DRY RUN — would set HOST1_OWNER = gmer4lfe…
⚠  [WARN]  DRY RUN — would set HOST1_RADARR_API_KEY = d43a3ec6…
⚠  [WARN]  DRY RUN — would set HOST1_RADARR_URL = http://l…
⚠  [WARN]  DRY RUN — would set HOST1_RADARR_MOVIE_ROOT = /movies…
⚠  [WARN]  DRY RUN — would set HOST1_SONARR_API_KEY = 130decd3…
⚠  [WARN]  DRY RUN — would set HOST1_SONARR_URL = http://l…
⚠  [WARN]  DRY RUN — would set HOST1_SONARR_TV_ROOT = /tv…
⚠  [WARN]  DRY RUN — would set HOST1_LIDARR_API_KEY = b2977e71…
⚠  [WARN]  DRY RUN — would set HOST1_LIDARR_URL = http://l…
⚠  [WARN]  DRY RUN — would set HOST1_LIDARR_MUSIC_ROOT = /ext-mus…
⚠  [WARN]  DRY RUN — would set HOST1_SABNZBD_URL = http://l…
⚠  [WARN]  DRY RUN — would set HOST1_SLSKD_URL = http://l…
⚠  [WARN]  DRY RUN — would set HOST1_QBIT_URL = http://l…
⚠  [WARN]  DRY RUN — would set HOST1_QBIT_USERNAME = root…
⚠  [WARN]  DRY RUN — would set HOST1_EMBY_CONTAINER = Emby…
⚠  [WARN]  DRY RUN — would set HOST1_EMBY_URL = http://l…
⚠  [WARN]  DRY RUN — would set HOST1_JELLYFIN_CONTAINER = Jellyfin…
⚠  [WARN]  DRY RUN — would set HOST1_JELLYFIN_URL = http://l…
⚠  [WARN]  DRY RUN — would set HOST1_SYS_WATCHDOG_NIC = br0…

━━━━━ 📋 Conf Populate Summary ━━━━━
  Updated: 0 field(s)
  Skipped: 0 already set
  Conf:    /boot/config/plugins/varaverk/Configurations/host1.conf
━━━━━━━━━━━━━━━━━━━━━


Clean — 20 fields detected, no errors. A few things to note:

- **SABNZBD_API_KEY / SLSKD_API_KEY** not shown — their containers are probably not running or the config file wasn't found; URL-only detection still worked for both
- **QBIT_PASSWORD** silently skipped — qBit is using PBKDF2 hashing (the `@ByteArray` format), which is correct behavior; username still captured
- **TRANSCODE_SSD** not detected — your Emby/Jellyfin containers likely don't have a `/transcode` destination mount (transcodes may be handled via a different path or inline config)

Everything else resolves cleanly: owner, all arr keys + URLs + media roots, SABnzbd/slskd URLs, qBit URL + username, Emby/Jellyfin names + URLs, NIC.


how much of the host.conf does that take care of