Files
Varaverk/Tools/recreate_shares.sh
T
Gmer4Lfe e13f2fa14f feat: slskd reconnect guard in downloaders_reset, mass v2 sync
- downloaders_reset: connection check block before slskd API sections;
  triggers PUT /api/v0/server reconnect if disconnected, polls 60s,
  gates Stuck Searches and Dead Transfer Records on SLSKD_CONNECTED
- Sync all modified/new/deleted files from v2 refactor across Docker_Essentials,
  Media, Monitors, Partnership, Rsync, Tools, Transcodes, unRAID_Essentials,
  common.sh, master confs, and new Manual/README docs
2026-05-19 20:00:10 -04:00

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
log "$ICON_DONE Status: done — ${#CREATED[@]} created, ${#SKIPPED[@]} skipped"
fi
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"