No setup banners to remove (Tools folder was already clean). Fix log→echo on summary status/conclusion lines across container_data_export, emby_database_repair, recreate_shares, zfs_pool_scrub, and fallback_state_reset (safety check + two summary detail lines). Fix log→echo on status display lines in watchdog_skip_list_manager (skip list empty, restart history lines). Move per-item "to add" preview echoes to log in emby_to_lidarr/radarr/sonarr_sync; fix nothing-to-do log→echo in all three. Add output tiers section to Manual-Tools.md.
298 lines
12 KiB
Bash
298 lines
12 KiB
Bash
#!/bin/bash
|
|
# ==============================================================================================
|
|
# ============================= Recreate Shares ================================================
|
|
# ==============================================================================================
|
|
#
|
|
# PURPOSE
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
# Creates share directories on the correct disks after a fresh unRAID install
|
|
# or disk rebuild. Reads all .cfg files from /boot/config/shares/ and creates
|
|
# the corresponding directories on each disk listed in the shareInclude setting.
|
|
# The array must be started before running — /mnt/user must be mounted.
|
|
#
|
|
# Typically run on HOST2 after a full disk replacement or fresh install where
|
|
# share folders were lost but /boot/config/shares/*.cfg files were restored.
|
|
#
|
|
# ==============================================================================================
|
|
# OPERATIONAL MODEL
|
|
# ==============================================================================================
|
|
#
|
|
# For each share .cfg file:
|
|
# 1. Reads shareInclude= to determine which disks own this share
|
|
# 2. Creates /mnt/diskN/ShareName/ on each listed disk if it doesn't exist
|
|
# 3. Places a .recovery marker file in /mnt/user/ShareName/ via the union filesystem
|
|
#
|
|
# .recovery Marker
|
|
# Signals to rsync.sh that this is a fresh share with no existing data.
|
|
# rsync.sh checks for .recovery before running with --delete:
|
|
# .recovery present → rsync WITHOUT --delete (new files only, nothing removed)
|
|
# .recovery absent → rsync WITH --delete (normal mirror mode)
|
|
#
|
|
# Self-cleaning: after the first successful rsync the source side has no .recovery
|
|
# file, so the second nightly run deletes it from the mirror, restoring normal
|
|
# --delete behaviour automatically. No manual cleanup needed.
|
|
#
|
|
# ==============================================================================================
|
|
# OPERATIONAL SAFEGUARDS
|
|
# ==============================================================================================
|
|
#
|
|
# Single Instance Lock
|
|
# acquire_lock prevents duplicate runs placing duplicate .recovery markers.
|
|
#
|
|
# Root Required
|
|
# mkdir on /mnt/diskN requires root.
|
|
#
|
|
# Array Mount Check
|
|
# Exits cleanly if the array is not started — /mnt/user not mounted means
|
|
# all share operations would fail silently.
|
|
#
|
|
# Per-Disk Guards
|
|
# Missing disks are skipped with a warning and the rest continue — a single
|
|
# offline disk does not abort the full run.
|
|
#
|
|
# Empty Config Guard
|
|
# Warns if no share .cfg files are found — catches the case where
|
|
# /boot/config/shares/ was not restored.
|
|
#
|
|
# Notification Validated
|
|
# validate_unraid_cmd confirms the notify script is present before use.
|
|
#
|
|
# ==============================================================================================
|
|
# RUNTIME MODES
|
|
# ==============================================================================================
|
|
#
|
|
# recreate_shares.sh
|
|
# Read all .cfg files, create share directories, place .recovery markers.
|
|
#
|
|
# recreate_shares.sh --dry-run
|
|
# Show what directories and markers would be created. No changes.
|
|
#
|
|
# recreate_shares.sh --log
|
|
# Verbose output per share and per disk.
|
|
#
|
|
# recreate_shares.sh --status
|
|
# Show which shares exist in config and which directories exist on disk.
|
|
#
|
|
# ==============================================================================================
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
|
|
source "$SCRIPT_DIR/../load_config.sh"
|
|
|
|
parse_args "$@"
|
|
|
|
SHARE_CFG_DIR="/boot/config/shares"
|
|
MARKER_FILE=".recovery"
|
|
|
|
CREATED=()
|
|
SKIPPED=()
|
|
FAILED=()
|
|
|
|
# ==============================================================================================
|
|
# ━━━ Setup ━━━
|
|
# ==============================================================================================
|
|
if [[ "$EUID" -ne 0 ]]; then
|
|
error "Must be run as root — mkdir on /mnt/diskN requires root"
|
|
exit 1
|
|
fi
|
|
|
|
validate_unraid_cmd \
|
|
"/usr/local/emhttp/plugins/dynamix/scripts/notify" \
|
|
"" "" \
|
|
"unRAID notify script" || warn "unRAID notify script not found — native notifications disabled"
|
|
|
|
acquire_lock
|
|
|
|
# detect_hosts() sets MY_ID — used in summary
|
|
detect_hosts
|
|
|
|
[[ "$DRY_RUN" == true ]] && warn "DRY RUN — no directories or markers will be created"
|
|
|
|
# ==============================================================================================
|
|
# ━━━ Status ━━━
|
|
# ==============================================================================================
|
|
if [[ "$SHOW_STATUS" == true ]]; then
|
|
echo ""
|
|
echo "━━━━━ $ICON_SUMMARY STATUS ━━━━━"
|
|
echo "$ICON_HOST Identity: $MY_ID ($LOCAL_SERVER_NAME)"
|
|
echo "$ICON_DISK Cfg dir: $SHARE_CFG_DIR"
|
|
echo "$ICON_DISK Marker: $MARKER_FILE"
|
|
echo ""
|
|
|
|
if ! mountpoint -q /mnt/user; then
|
|
warn "Array: NOT STARTED — /mnt/user not mounted"
|
|
else
|
|
echo " Array: started ✅"
|
|
fi
|
|
|
|
echo ""
|
|
echo "━━━ Share Config Files ━━━"
|
|
CFG_COUNT=0
|
|
for cfg in "$SHARE_CFG_DIR"/*.cfg; do
|
|
[[ ! -f "$cfg" ]] && continue
|
|
(( CFG_COUNT++ ))
|
|
SHARE_NAME=$(basename "$cfg" .cfg)
|
|
INCLUDE=$(grep '^shareInclude=' "$cfg" 2>/dev/null | cut -d'"' -f2)
|
|
MARKER_EXISTS="no"
|
|
[[ -f "/mnt/user/${SHARE_NAME}/${MARKER_FILE}" ]] && MARKER_EXISTS="yes"
|
|
echo " $ICON_DISK $SHARE_NAME — disks: ${INCLUDE:-none} — recovery marker: $MARKER_EXISTS"
|
|
done
|
|
[[ "$CFG_COUNT" -eq 0 ]] && warn "No .cfg files found in $SHARE_CFG_DIR"
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━"
|
|
exit 0
|
|
fi
|
|
|
|
# ==============================================================================================
|
|
# ━━━ Pre-flight ━━━
|
|
# ==============================================================================================
|
|
# Array must be started — /mnt/user must be mounted
|
|
if ! mountpoint -q /mnt/user; then
|
|
error "Array is not started — /mnt/user is not mounted"
|
|
warn "Start the array in the unRAID UI before running this script"
|
|
notify "Recreate shares failed on $(hostname) — array is not started" \
|
|
"Recreate Shares" "warning"
|
|
exit 1
|
|
fi
|
|
log "Array is started — /mnt/user is mounted ✅"
|
|
|
|
# Check share cfg directory exists and has files
|
|
if [[ ! -d "$SHARE_CFG_DIR" ]]; then
|
|
error "Share config directory not found: $SHARE_CFG_DIR"
|
|
error "Is /boot mounted? Is this the correct server?"
|
|
exit 1
|
|
fi
|
|
|
|
CFG_FILES=("$SHARE_CFG_DIR"/*.cfg)
|
|
if [[ ! -f "${CFG_FILES[0]}" ]]; then
|
|
warn "No share .cfg files found in $SHARE_CFG_DIR"
|
|
warn "Nothing to recreate — are share configs present on /boot?"
|
|
exit 0
|
|
fi
|
|
|
|
log "Found ${#CFG_FILES[@]} share .cfg file(s) in $SHARE_CFG_DIR"
|
|
|
|
# ==============================================================================================
|
|
# ━━━ Recreate Shares ━━━
|
|
# ==============================================================================================
|
|
echo ""
|
|
echo "━━━ $ICON_DISK Recreate Shares — $MY_ID ━━━"
|
|
echo ""
|
|
|
|
for cfg in "${CFG_FILES[@]}"; do
|
|
[[ ! -f "$cfg" ]] && continue
|
|
SHARE_NAME=$(basename "$cfg" .cfg)
|
|
INCLUDE=$(grep '^shareInclude=' "$cfg" 2>/dev/null | cut -d'"' -f2)
|
|
|
|
echo "━━━ $ICON_DISK $SHARE_NAME ━━━"
|
|
|
|
if [[ -z "$INCLUDE" ]]; then
|
|
warn "$SHARE_NAME — no shareInclude in .cfg — skipping"
|
|
SKIPPED+=("$SHARE_NAME")
|
|
echo ""
|
|
continue
|
|
fi
|
|
|
|
log "$SHARE_NAME — disks: $INCLUDE"
|
|
|
|
SHARE_OK=true
|
|
DIRS_CREATED=0
|
|
DIRS_EXISTED=0
|
|
|
|
# Create directory on each listed disk
|
|
IFS=',' read -ra DISKS <<< "$INCLUDE"
|
|
for disk in "${DISKS[@]}"; do
|
|
disk="${disk// /}" # trim whitespace
|
|
[[ -z "$disk" ]] && continue
|
|
|
|
DISK_MOUNT="/mnt/${disk}"
|
|
DISK_PATH="${DISK_MOUNT}/${SHARE_NAME}"
|
|
|
|
# Verify disk is mounted
|
|
if ! mountpoint -q "$DISK_MOUNT" 2>/dev/null; then
|
|
warn "$disk not mounted — skipping $DISK_PATH"
|
|
continue
|
|
fi
|
|
|
|
if [[ -d "$DISK_PATH" ]]; then
|
|
log "$disk/$SHARE_NAME already exists — skipping"
|
|
(( DIRS_EXISTED++ ))
|
|
else
|
|
if [[ "$DRY_RUN" == true ]]; then
|
|
warn "DRY RUN — would create: $DISK_PATH"
|
|
(( DIRS_CREATED++ ))
|
|
elif mkdir -p "$DISK_PATH"; then
|
|
log "Created: $DISK_PATH ✅"
|
|
(( DIRS_CREATED++ ))
|
|
else
|
|
error "Failed to create: $DISK_PATH"
|
|
SHARE_OK=false
|
|
fi
|
|
fi
|
|
done
|
|
|
|
# Place .recovery marker via /mnt/user (union filesystem)
|
|
MARKER_PATH="/mnt/user/${SHARE_NAME}/${MARKER_FILE}"
|
|
|
|
if [[ "$SHARE_OK" == true ]]; then
|
|
if [[ -f "$MARKER_PATH" ]]; then
|
|
log ".recovery marker already exists in $SHARE_NAME"
|
|
CREATED+=("$SHARE_NAME")
|
|
elif [[ "$DRY_RUN" == true ]]; then
|
|
warn "DRY RUN — would place marker: $MARKER_PATH"
|
|
CREATED+=("$SHARE_NAME")
|
|
elif touch "$MARKER_PATH" 2>/dev/null; then
|
|
log "Marker placed: $MARKER_PATH ✅"
|
|
CREATED+=("$SHARE_NAME")
|
|
else
|
|
warn "$SHARE_NAME — could not place .recovery marker"
|
|
warn "Share directory may not be visible via /mnt/user yet"
|
|
warn "Try: touch /mnt/user/${SHARE_NAME}/.recovery manually after verifying share"
|
|
SKIPPED+=("$SHARE_NAME")
|
|
fi
|
|
else
|
|
FAILED+=("$SHARE_NAME")
|
|
fi
|
|
|
|
[[ "$DIRS_CREATED" -gt 0 ]] && warn "$SHARE_NAME — created $DIRS_CREATED dir(s) on disk"
|
|
[[ "$DIRS_EXISTED" -gt 0 ]] && log "$SHARE_NAME — $DIRS_EXISTED dir(s) already existed"
|
|
echo ""
|
|
done
|
|
|
|
# ==============================================================================================
|
|
# ━━━ Summary ━━━
|
|
# ==============================================================================================
|
|
echo "━━━━━ $ICON_SUMMARY RECREATE SHARES SUMMARY ━━━━━"
|
|
echo "$ICON_HOST Identity: $MY_ID ($LOCAL_SERVER_NAME)"
|
|
echo ""
|
|
|
|
[[ ${#CREATED[@]} -gt 0 ]] && warn "Created + marked: ${CREATED[*]}"
|
|
[[ ${#SKIPPED[@]} -gt 0 ]] && warn "Skipped: ${SKIPPED[*]}"
|
|
[[ ${#FAILED[@]} -gt 0 ]] && echo "$ICON_ERROR Failed: ${FAILED[*]}"
|
|
|
|
echo ""
|
|
echo " Created: ${#CREATED[@]}"
|
|
echo " Skipped: ${#SKIPPED[@]}"
|
|
echo " Failed: ${#FAILED[@]}"
|
|
|
|
if [[ "$DRY_RUN" == false && ${#CREATED[@]} -gt 0 ]]; then
|
|
echo ""
|
|
echo "━━━ Next Steps ━━━"
|
|
echo " 1. $ICON_HEALTH Verify shares are visible in unRAID UI"
|
|
echo " 2. $ICON_SYNC Run initial rsync push from HOST1 → HOST2"
|
|
echo " rsync.sh will detect .recovery markers and skip --delete"
|
|
echo " Normal --delete mode restores automatically on second nightly run"
|
|
echo " 3. $ICON_GEAR No manual config changes needed — markers self-clean ✅"
|
|
fi
|
|
|
|
echo ""
|
|
if [[ "$DRY_RUN" == true ]]; then
|
|
warn "DRY RUN — no changes made"
|
|
elif [[ ${#FAILED[@]} -gt 0 ]]; then
|
|
echo "$ICON_ERROR Status: completed with failures"
|
|
notify "Recreate shares failed on $(hostname) ($MY_ID) — failed: ${FAILED[*]}" \
|
|
"Recreate Shares" "warning"
|
|
exit 1
|
|
else
|
|
echo "$ICON_DONE Status: done — ${#CREATED[@]} created, ${#SKIPPED[@]} skipped"
|
|
fi
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" |