Files
Varaverk/Tools/recreate_shares.sh
T

273 lines
12 KiB
Bash

#!/bin/bash
# ==============================================================================================
# ============================= Recreate Shares ================================================
# ==============================================================================================
# 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.
#
# ── WHEN TO USE ───────────────────────────────────────────────────────────────────────────────
# Run directly on HOST2 after array is started following:
# - A full disk replacement or rebuild where share folders were lost
# - A fresh unRAID install where /boot/config/shares/*.cfg files were restored
# - Any situation where the share folder structure exists in config but not on disk
#
# The array must be started before running this script — /mnt/user must be mounted.
#
# ── WHAT IT DOES ──────────────────────────────────────────────────────────────────────────────
# 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 FILE ─────────────────────────────────────────────────────────────────────
# The .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 (safe — new files only, nothing removed)
# .recovery absent → rsync WITH --delete (normal — mirror mode)
#
# The marker self-cleans: after the first successful rsync the source side has no .recovery
# file so the second nightly run will delete it from the mirror, restoring normal --delete
# behaviour automatically. No manual cleanup needed. ✅
#
# ── HOST AWARENESS ────────────────────────────────────────────────────────────────────────────
# This script runs on the server that needs shares recreated — typically HOST2 during rebuild.
# detect_hosts() sets MY_ID for output clarity.
#
# ── SAFEGUARDS ────────────────────────────────────────────────────────────────────────────────
# acquire_lock — prevents duplicate runs placing duplicate markers
# Root check — mkdir on /mnt/diskN requires root
# Array mount check — exits cleanly if array not started
# Empty cfg guard — warns if no share cfg files found
# Per-disk guards — skips missing disks with warning, continues others
# validate_unraid_cmd — notify validated before use
# Silent on success — only failures produce visible output
#
# ── USAGE ─────────────────────────────────────────────────────────────────────────────────────
# recreate_shares.sh — create all shares from .cfg files
# recreate_shares.sh --dry-run — preview what would be created, no changes
# recreate_shares.sh --log — verbose output per disk
# recreate_shares.sh --status — show current share state and exit
# ==============================================================================================
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 "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"