feat: Tailscale resolution hardening, partnership offboard completion, Emby provisioning

common.sh:
- Add resolve_tailscale_ip() helper — tries `tailscale ip -4` first, falls back to
  parsing `tailscale status` output; handles hosts where MagicDNS short-name resolution
  is not active
- Add PARTNERSHIP_OWN_CONTAINERS alias in detect_hosts()
- Add aliasing for 4 Emby provisioning vars (PARTNERSHIP_PROVISION_EMBY_ADMIN,
  PARTNERSHIP_EMBY_ADMIN_USER, PARTNERSHIP_EMBY_ADMIN_PASS, PARTNERSHIP_EMBY_PORT)

Partnership/partnership_manager.sh:
- Replace 9 bare `tailscale ip -4` calls with resolve_tailscale_ip()
- Add read_remote_conf_var() and read_remote_conf_array() — SSH to mirror, source its
  own load_config.sh + detect_hosts(), return aliased variable; solves sparse-checkout
  problem where HOST1 cannot read master_host2.conf directly
- Add derive_short_name() — strips unraid- prefix, capitalises first char
- Add cleanup_partner_containers() — removes partner containers via FolderView3 folder
  if enabled, else falls back to FALLBACK_*_COVERS_*_TIER* arrays
- Add cleanup_owner_containers_on_mirror() — SSH to mirror, stops and removes containers
  matching *-${OWNER_SHORT} naming convention
- Add start_own_stack() and start_mirror_own_stack() — restart own containers locally
  or on mirror via SSH using PARTNERSHIP_OWN_CONTAINERS
- Add provision_emby_admin() — reads mirror credentials via read_remote_conf_var, checks
  for username collision, creates user + sets password + grants admin policy via Emby API
- Add revoke_emby_admin() — looks up mirror username on local Emby, deletes via REST API
- Wire offboard paths (both mirror-initiated and owner-initiated) to call container
  cleanup and stack restart; update --check finalisation paths accordingly
- Fix write_state_file in --onboard not gated on DRY_RUN (was writing ACTIVE state on
  dry runs)

master_host1.conf:
- Add HOST1_PARTNERSHIP_OWN_CONTAINERS array
- Add partnership Emby provisioning config (toggle + port + per-host credentials)

master_host2.conf:
- Add HOST2_PARTNERSHIP_OWN_CONTAINERS array
- Add HOST2_PARTNERSHIP_EMBY_ADMIN_USER and HOST2_PARTNERSHIP_EMBY_ADMIN_PASS

Tailscale fix applied to:
- Initial_run/ssh_setup.sh (2 callsites)
- unRAID_Essentials/rsync_stop.sh (1 callsite)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Gmer4Lfe
2026-05-10 20:09:13 -04:00
co-authored by Claude Sonnet 4.6
parent 6948755c86
commit 0ae31b5fa6
33 changed files with 1241 additions and 155 deletions
+62 -6
View File
@@ -58,12 +58,13 @@
# All aliased by detect_hosts() — script uses unprefixed names
#
# ── CONFIGURATION (master.conf) ───────────────────────────────────────────────────────────────
# RADARR_ORPHAN_AGE — days before untracked file eligible for deletion
# RADARR_MAX_DELETE_GB — require --i-know-what-im-doing above this
# RADARR_EXTENSIONS — video file extensions considered for orphan classification
# RADARR_PROTECTED_PATTERNS — file patterns never deleted
# RADARR_VERSION_MAJOR — expected Radarr major version for API safety check
# ARR_CLEANUP_STATS — stats file path (read by coffee report)
# RADARR_ORPHAN_AGE — days before untracked file eligible for deletion
# RADARR_MAX_DELETE_GB — require --i-know-what-im-doing above this
# RADARR_EXTENSIONS — video file extensions considered for orphan classification
# RADARR_PROTECTED_PATTERNS — file patterns never deleted
# RADARR_VERSION_MAJOR — expected Radarr major version for API safety check
# RADARR_IMPORT_SCAN_TIMEOUT — seconds to wait for pre-flight import scan (default 600)
# ARR_CLEANUP_STATS — stats file path (read by coffee report)
#
# ── USAGE ─────────────────────────────────────────────────────────────────────────────────────
# radarr_cleanup.sh — normal run
@@ -282,6 +283,61 @@ format_bytes() {
fi
}
# ==============================================================================================
# ━━━ Pre-flight: Radarr Import Scan ━━━
# ==============================================================================================
echo ""
echo "━━━ $ICON_SYNC Pre-flight: Radarr Import Scan ━━━"
# Reverse-lookup container path from path map so Radarr gets its own path, not the host path
RADARR_CONTAINER_ROOT=""
for _cp in "${!ARR_PATH_MAP[@]}"; do
if [[ "${ARR_PATH_MAP[$_cp]}" == "$RADARR_MOVIES_ROOT" ]]; then
RADARR_CONTAINER_ROOT="$_cp"
break
fi
done
unset _cp
if [[ -n "$RADARR_CONTAINER_ROOT" ]]; then
log "Triggering DownloadedMoviesScan on: $RADARR_CONTAINER_ROOT"
SCAN_PAYLOAD="{\"name\": \"DownloadedMoviesScan\", \"path\": \"$RADARR_CONTAINER_ROOT\"}"
else
log "No path map match — triggering DownloadedMoviesScan (all root folders)"
SCAN_PAYLOAD='{"name": "DownloadedMoviesScan"}'
fi
SCAN_RESPONSE=$(curl -sf --max-time 30 -X POST \
-H "X-Api-Key: $RADARR_API_KEY" \
-H "Content-Type: application/json" \
-d "$SCAN_PAYLOAD" \
"${RADARR_URL}/api/v3/command" 2>/dev/null)
SCAN_CMD_ID=$(echo "$SCAN_RESPONSE" | jq -r '.id // empty' 2>/dev/null)
if [[ -z "$SCAN_CMD_ID" ]]; then
warn "Could not trigger import scan — proceeding without pre-flight"
else
echo " Import scan queued (command ID: $SCAN_CMD_ID) — waiting for completion..."
POLL_TIMEOUT=${RADARR_IMPORT_SCAN_TIMEOUT:-600}
POLLED=0
while [[ "$POLLED" -lt "$POLL_TIMEOUT" ]]; do
SCAN_STATUS=$(curl -sf --max-time 10 \
-H "X-Api-Key: $RADARR_API_KEY" \
"${RADARR_URL}/api/v3/command/${SCAN_CMD_ID}" 2>/dev/null | \
jq -r '.status // empty' 2>/dev/null)
case "$SCAN_STATUS" in
completed) log "Import scan complete ✅"; break ;;
failed) warn "Import scan reported failed — proceeding anyway"; break ;;
esac
sleep 10
(( POLLED += 10 ))
[[ $(( POLLED % 60 )) -eq 0 ]] && log " Still scanning... (${POLLED}s elapsed)"
done
[[ "$POLLED" -ge "$POLL_TIMEOUT" ]] && \
warn "Import scan timed out after ${POLL_TIMEOUT}s — proceeding anyway"
fi
# ==============================================================================================
# ━━━ Fetch Radarr Tracked Files ━━━
# ==============================================================================================