v2 post-migration bug fixes, lidarr_missing_art port, and Partnership SSH/FolderView3 enhancements
Bug fixes across the ecosystem after v1→v2 architecture migration and Unraid 7.2.5 upgrade:
- common.sh: fix _alias_array() phantom empty element (removed [@]:-} pattern), fix
resolve_remote_ip() with Tailscale FQDN lowercase + awk fallback, add FANART/LASTFM
key aliases in detect_hosts(), global [@]:-} sweep across 10+ scripts
- webgui_restart.sh: fix emhttp detection (pgrep emhttpd) and restart command for 7.2.5
(/usr/local/sbin/emhttp stop && start — rc.emhttp removed in 7.2.5)
- bandwidth_monitor.sh, continuous_scripts_status.sh: fix 'local' keyword outside function
- backup_verify.sh: fix resolve_remote_ip() called before detect_hosts()
- Orchestrators: fix script display duplication bug in status output (${entry##*/})
- rsync_stop.sh, git_pull_execute.sh, partnership_manager.sh, coffee_report: lowercase all
tailscale ip -4 call sites to match Tailscale's lowercase device names
- master_host1.conf: fix SSH key path (gmer4lfe_rsync_automation), add FANART/LASTFM keys
- master_host2.conf: add FANART/LASTFM API keys
New: Media/lidarr_missing_art.sh
- Full ecosystem port of standalone Lidarr artwork fetcher
- Fetches missing album art via fanart.tv + Last.fm APIs
- @tsv batch extraction: 1 jq call per API response vs N*albums (8050 albums in 24s)
- HOST guard (HOST1 only), --status, --dry-run, acquire_lock
New: Initial_run/ssh_setup.sh
- Generates {hostname}_rsync_automation ed25519 keypair (skip if exists, --force to regen)
- ssh-copy-id to remote via Tailscale IP, auto-updates master_host*.conf
- --validate mode: strike tracking (SSH_MAX_STRIKES, SSH_STRIKE_RESET_HRS),
notify at limit — Tailscale-unreachable remote does NOT count as SSH strike
New: Initial_run/partnership_onboard.sh
- Orchestrator: ssh_setup.sh then partnership_manager.sh --onboard in one command
Partnership/partnership_manager.sh: FolderView3 integration
- Derive partner folder name at runtime (strip unraid- prefix case-insensitively)
- --onboard: create {Mirror}-Failover folder with failover tier containers
- --offboard (both paths): stop + rm containers in folder, remove JSON entry
- --check: calls ssh_setup.sh --validate when IP resolves but SSH state empty
- --status: shows FolderView3 folder state and containers inline
master.conf: SSH_MAX_STRIKES, SSH_STRIKE_RESET_HRS, PARTNERSHIP_FOLDERVIEW3,
PARTNERSHIP_FOLDERVIEW3_URL added to Partnership section
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
7cecda6b90
commit
5526bc1eca
@@ -87,7 +87,7 @@ if [[ "$SHOW_STATUS" == true ]]; then
|
||||
if [[ ${#CRITICAL_SYNC_SHARES[@]} -eq 0 ]]; then
|
||||
warn " No CRITICAL_SYNC_SHARES configured"
|
||||
else
|
||||
for share in "${CRITICAL_SYNC_SHARES[@]:-}"; do
|
||||
for share in "${CRITICAL_SYNC_SHARES[@]}"; do
|
||||
[[ -z "$share" ]] && continue
|
||||
SHARE_PATH="${share%%|*}"
|
||||
SHARE_PROFILE="${share##*|}"
|
||||
@@ -102,7 +102,7 @@ if [[ "$SHOW_STATUS" == true ]]; then
|
||||
if [[ ${#CRITICAL_MAINTENANCE_SCRIPTS[@]} -eq 0 ]]; then
|
||||
echo " None configured"
|
||||
else
|
||||
for entry in "${CRITICAL_MAINTENANCE_SCRIPTS[@]:-}"; do
|
||||
for entry in "${CRITICAL_MAINTENANCE_SCRIPTS[@]}"; do
|
||||
[[ -z "$entry" || "$entry" == \#* ]] && continue
|
||||
echo " $ICON_GEAR $(basename "${entry%% *}")"
|
||||
done
|
||||
@@ -131,7 +131,7 @@ else
|
||||
RSYNC_DRY=""
|
||||
[[ "$DRY_RUN" == true ]] && RSYNC_DRY="--dry-run"
|
||||
|
||||
for share in "${CRITICAL_SYNC_SHARES[@]:-}"; do
|
||||
for share in "${CRITICAL_SYNC_SHARES[@]}"; do
|
||||
[[ -z "$share" ]] && continue
|
||||
|
||||
# Parse optional profile flag: "/path/to/share|profile-name"
|
||||
|
||||
@@ -100,17 +100,17 @@ if [[ "$SHOW_STATUS" == true ]]; then
|
||||
echo "$ICON_SYNC Daily enabled: ${DAILY_RSYNC_ENABLED:-false}"
|
||||
echo ""
|
||||
echo "━━━ Daily Sync Shares ━━━"
|
||||
for share in "${DAILY_SYNC_SHARES[@]:-}"; do
|
||||
for share in "${DAILY_SYNC_SHARES[@]}"; do
|
||||
[[ -n "$share" ]] && echo " $ICON_SYNC $share"
|
||||
done
|
||||
for share in "${PERSONAL_SHARES[@]:-}"; do
|
||||
for share in "${PERSONAL_SHARES[@]}"; do
|
||||
[[ -n "$share" ]] && echo " $ICON_SYNC $share (personal)"
|
||||
done
|
||||
echo ""
|
||||
echo "━━━ Daily Maintenance Scripts ━━━"
|
||||
for entry in "${DAILY_MAINTENANCE_SCRIPTS[@]:-}"; do
|
||||
for entry in "${DAILY_MAINTENANCE_SCRIPTS[@]}"; do
|
||||
[[ -z "$entry" ]] && continue
|
||||
echo " $ICON_GEAR $(basename "${entry%% *}") ${entry#* }"
|
||||
echo " $ICON_GEAR ${entry##*/}"
|
||||
done
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
exit 0
|
||||
@@ -122,10 +122,10 @@ fi
|
||||
# No manual HOST1/HOST2 comparison needed — aliased automatically per server
|
||||
# ==============================================================================================
|
||||
ALL_SHARES=()
|
||||
for share in "${DAILY_SYNC_SHARES[@]:-}"; do
|
||||
for share in "${DAILY_SYNC_SHARES[@]}"; do
|
||||
[[ -n "$share" ]] && ALL_SHARES+=("$share")
|
||||
done
|
||||
for share in "${PERSONAL_SHARES[@]:-}"; do
|
||||
for share in "${PERSONAL_SHARES[@]}"; do
|
||||
[[ -n "$share" ]] && ALL_SHARES+=("$share")
|
||||
done
|
||||
SHARE_COUNT=${#ALL_SHARES[@]}
|
||||
@@ -133,7 +133,7 @@ SHARE_COUNT=${#ALL_SHARES[@]}
|
||||
# ── Split maintenance scripts: git pull runs pre-sync, rest run post-sync ─────────────────────
|
||||
PRE_SYNC_SCRIPTS=()
|
||||
POST_SYNC_SCRIPTS=()
|
||||
for script_entry in "${DAILY_MAINTENANCE_SCRIPTS[@]:-}"; do
|
||||
for script_entry in "${DAILY_MAINTENANCE_SCRIPTS[@]}"; do
|
||||
[[ -z "$script_entry" ]] && continue
|
||||
script_name=$(basename "${script_entry%% *}")
|
||||
if [[ "$script_name" == "git_pull_execute.sh" ]]; then
|
||||
|
||||
@@ -278,7 +278,7 @@ if command -v zpool >/dev/null 2>&1; then
|
||||
while IFS=$'\t' read -r pool health; do
|
||||
[[ -z "$pool" ]] && continue
|
||||
SKIP=false
|
||||
for ignore in "${ZFS_REPORT_IGNORE_POOLS[@]:-}"; do
|
||||
for ignore in "${ZFS_REPORT_IGNORE_POOLS[@]}"; do
|
||||
[[ "$pool" == "$ignore" ]] && SKIP=true && break
|
||||
done
|
||||
[[ "$SKIP" == true ]] && continue
|
||||
@@ -297,7 +297,7 @@ if command -v smartctl >/dev/null 2>&1; then
|
||||
[[ ! -e "$disk" ]] && continue
|
||||
DISK_NAME=$(basename "$disk")
|
||||
SKIP=false
|
||||
for ignore in "${SMART_IGNORE_DRIVES[@]:-}"; do
|
||||
for ignore in "${SMART_IGNORE_DRIVES[@]}"; do
|
||||
[[ "$DISK_NAME" == "$ignore" ]] && SKIP=true && break
|
||||
done
|
||||
[[ "$SKIP" == true ]] && continue
|
||||
@@ -563,7 +563,7 @@ if command -v docker >/dev/null 2>&1; then
|
||||
while IFS= read -r name; do
|
||||
[[ -z "$name" ]] && continue
|
||||
SKIP=false
|
||||
for ignore in "${WATCHDOG_SCAN_IGNORE[@]:-}"; do
|
||||
for ignore in "${WATCHDOG_SCAN_IGNORE[@]}"; do
|
||||
[[ "$name" == "$ignore" ]] && SKIP=true && break
|
||||
done
|
||||
[[ "$SKIP" == false ]] && STOPPED_NAMES+=("$name")
|
||||
@@ -618,7 +618,7 @@ case "$FO_STATE" in
|
||||
esac
|
||||
|
||||
if command -v tailscale >/dev/null 2>&1; then
|
||||
REMOTE_TS_IP=$(tailscale ip -4 "$REMOTE_SERVER_NAME" 2>/dev/null)
|
||||
REMOTE_TS_IP=$(tailscale ip -4 "${REMOTE_SERVER_NAME,,}" 2>/dev/null)
|
||||
if [[ -n "$REMOTE_TS_IP" ]]; then
|
||||
ping -c 1 -W 2 "$REMOTE_TS_IP" >/dev/null 2>&1 && \
|
||||
line " 🌐 $REMOTE_ID ($REMOTE_SERVER_NAME): $REMOTE_TS_IP — reachable ✅" || \
|
||||
@@ -638,7 +638,7 @@ if ! command -v openssl >/dev/null 2>&1; then
|
||||
elif [[ ${#CERT_MONITOR_DOMAINS[@]} -eq 0 ]]; then
|
||||
line "SSL certs: no domains configured (CERT_MONITOR_DOMAINS empty)"
|
||||
else
|
||||
for domain in "${CERT_MONITOR_DOMAINS[@]:-}"; do
|
||||
for domain in "${CERT_MONITOR_DOMAINS[@]}"; do
|
||||
[[ -z "$domain" ]] && continue
|
||||
EXPIRY=$(echo | timeout "${CERT_TIMEOUT:-10}" openssl s_client \
|
||||
-connect "${domain}:443" -servername "$domain" 2>/dev/null | \
|
||||
@@ -757,7 +757,7 @@ if command -v smartctl >/dev/null 2>&1; then
|
||||
[[ ! -e "$disk" ]] && continue
|
||||
DISK_NAME=$(basename "$disk")
|
||||
SKIP=false
|
||||
for ignore in "${SMART_IGNORE_DRIVES[@]:-}"; do
|
||||
for ignore in "${SMART_IGNORE_DRIVES[@]}"; do
|
||||
[[ "$DISK_NAME" == "$ignore" ]] && SKIP=true && break
|
||||
done
|
||||
[[ "$SKIP" == true ]] && continue
|
||||
|
||||
@@ -143,7 +143,7 @@ if [[ "$SHOW_STATUS" == true ]]; then
|
||||
if [[ ${#WEEKLY_SYNC_SHARES[@]} -eq 0 ]]; then
|
||||
warn " No WEEKLY_SYNC_SHARES configured"
|
||||
else
|
||||
for share in "${WEEKLY_SYNC_SHARES[@]:-}"; do
|
||||
for share in "${WEEKLY_SYNC_SHARES[@]}"; do
|
||||
[[ -n "$share" ]] && echo " $ICON_SYNC $(basename "$share") ($share)"
|
||||
done
|
||||
fi
|
||||
@@ -152,13 +152,13 @@ if [[ "$SHOW_STATUS" == true ]]; then
|
||||
if [[ ${#WEEKLY_MAINTENANCE_SCRIPTS[@]} -eq 0 ]]; then
|
||||
echo " None configured"
|
||||
else
|
||||
for entry in "${WEEKLY_MAINTENANCE_SCRIPTS[@]:-}"; do
|
||||
[[ -n "$entry" ]] && echo " $ICON_GEAR $(basename "${entry%% *}") ${entry#* }"
|
||||
for entry in "${WEEKLY_MAINTENANCE_SCRIPTS[@]}"; do
|
||||
[[ -n "$entry" ]] && echo " $ICON_GEAR ${entry##*/}"
|
||||
done
|
||||
fi
|
||||
echo ""
|
||||
echo "━━━ Containers (from profile config) ━━━"
|
||||
for c in "${MAINTENANCE_CONTAINERS[@]:-}"; do
|
||||
for c in "${MAINTENANCE_CONTAINERS[@]}"; do
|
||||
[[ -n "$c" ]] && echo " $ICON_CONTAINERS $c"
|
||||
done
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
@@ -203,12 +203,12 @@ echo "━━━ $ICON_GEAR Container Updates ━━━"
|
||||
|
||||
if [[ "$WEEKLY_SYNC_UPDATES" == true ]]; then
|
||||
if [[ "$DRY_RUN" == true ]]; then
|
||||
for c in "${MAINTENANCE_CONTAINERS[@]:-}"; do
|
||||
for c in "${MAINTENANCE_CONTAINERS[@]}"; do
|
||||
[[ -n "$c" ]] && warn "DRY RUN — would pull: $c"
|
||||
done
|
||||
else
|
||||
log "Pulling local container updates..."
|
||||
for c in "${MAINTENANCE_CONTAINERS[@]:-}"; do
|
||||
for c in "${MAINTENANCE_CONTAINERS[@]}"; do
|
||||
[[ -z "$c" ]] && continue
|
||||
IMAGE=$(timeout "$DOCKER_TIMEOUT" docker inspect \
|
||||
"$c" --format '{{.Config.Image}}' 2>/dev/null)
|
||||
@@ -233,7 +233,7 @@ if [[ "$WEEKLY_SYNC_UPDATES_REMOTE" == true ]]; then
|
||||
warn "DRY RUN — would pull updates on $REMOTE_SERVER_NAME"
|
||||
else
|
||||
log "Pulling remote container updates on $REMOTE_SERVER_NAME..."
|
||||
for c in "${MAINTENANCE_CONTAINERS[@]:-}"; do
|
||||
for c in "${MAINTENANCE_CONTAINERS[@]}"; do
|
||||
[[ -z "$c" ]] && continue
|
||||
IMAGE=$(timeout "$SSH_TIMEOUT" ssh -i "$SSH_KEY" \
|
||||
-o ConnectTimeout="$SSH_TIMEOUT" \
|
||||
@@ -323,7 +323,7 @@ fi
|
||||
if [[ ${#WEEKLY_MAINTENANCE_SCRIPTS[@]} -gt 0 ]]; then
|
||||
echo ""
|
||||
echo "━━━ $ICON_GEAR Post-sync Jobs ━━━"
|
||||
for script_entry in "${WEEKLY_MAINTENANCE_SCRIPTS[@]:-}"; do
|
||||
for script_entry in "${WEEKLY_MAINTENANCE_SCRIPTS[@]}"; do
|
||||
[[ -z "$script_entry" ]] && continue
|
||||
echo ""
|
||||
run_job "$script_entry"
|
||||
@@ -344,15 +344,15 @@ echo "$ICON_GEAR Updates: local=${WEEKLY_SYNC_UPDATES:-false} remote=${WEEKL
|
||||
echo ""
|
||||
|
||||
echo "$ICON_SYNC Sync jobs ($SHARE_COUNT):"
|
||||
for job in "${PASS[@]:-}"; do echo " $ICON_DONE $job"; done
|
||||
for job in "${FAIL[@]:-}"; do echo " $ICON_ERROR $job"; done
|
||||
for job in "${PASS[@]}"; do echo " $ICON_DONE $job"; done
|
||||
for job in "${FAIL[@]}"; do echo " $ICON_ERROR $job"; done
|
||||
echo " Passed: ${#PASS[@]} Failed: ${#FAIL[@]}"
|
||||
|
||||
if [[ ${#JOB_PASS[@]} -gt 0 || ${#JOB_FAIL[@]} -gt 0 ]]; then
|
||||
echo ""
|
||||
echo "$ICON_GEAR Post-sync jobs:"
|
||||
for job in "${JOB_PASS[@]:-}"; do echo " $ICON_DONE $job"; done
|
||||
for job in "${JOB_FAIL[@]:-}"; do echo " $ICON_ERROR $job"; done
|
||||
for job in "${JOB_PASS[@]}"; do echo " $ICON_DONE $job"; done
|
||||
for job in "${JOB_FAIL[@]}"; do echo " $ICON_ERROR $job"; done
|
||||
fi
|
||||
|
||||
TOTAL_FAIL=$(( ${#FAIL[@]} + ${#JOB_FAIL[@]} ))
|
||||
|
||||
Reference in New Issue
Block a user