194 lines
9.0 KiB
Bash
194 lines
9.0 KiB
Bash
#!/bin/bash
|
|
# ==============================================================================================
|
|
# ============================= Bulk Permissions Repair ========================================
|
|
# ==============================================================================================
|
|
# Applies correct ownership and permissions to one or more specific paths.
|
|
# Faster than running media_shares_permissions.sh which processes all configured shares.
|
|
#
|
|
# ── WHEN TO USE ───────────────────────────────────────────────────────────────────────────────
|
|
# Use for targeted repair after:
|
|
# - A failed transfer that left files owned by wrong user (root:root from rsync)
|
|
# - A container writing as root instead of nobody:users — before PUID/PGID was fixed
|
|
# - Manual file copies that bypassed normal permission handling
|
|
# - A new share that needs permissions applied before the next nightly run
|
|
# - A large rsync that imported thousands of files before media_shares_permissions.sh ran
|
|
#
|
|
# ── PERMISSIONS MODEL ─────────────────────────────────────────────────────────────────────────
|
|
# Directories: PERMISSIONS_DIR_MODE (default 755)
|
|
# Owner (nobody) — rwx enter, list, create files
|
|
# Group (users) — r-x enter and list
|
|
# Others — r-x Samba guests can browse
|
|
#
|
|
# Files: PERMISSIONS_FILE_MODE (default 664)
|
|
# Owner (nobody) — rw read + write
|
|
# Group (users) — rw arrs can import and rename
|
|
# Others — r Samba guests can read
|
|
# No execute bit — media files are never executable
|
|
#
|
|
# ── DIAGNOSTIC — HIGH WRONG OWNER COUNT ───────────────────────────────────────────────────────
|
|
# This script counts files with wrong ownership before applying the fix.
|
|
# A high count on a share that was recently written → a container has wrong PUID/PGID.
|
|
# Fix: add PUID=99 PGID=100 to the container's Docker template.
|
|
# Common culprits: SABnzbd, qBittorrent, slskd.
|
|
#
|
|
# ── SAFEGUARDS ────────────────────────────────────────────────────────────────────────────────
|
|
# Root check — required for chown
|
|
# Path existence check — skips missing paths with error
|
|
# Separate passes — directories and files chmod'd separately for correctness
|
|
# validate_unraid_cmd — notify validated before use
|
|
# Silent on success — only failures produce visible output
|
|
#
|
|
# ── USAGE ─────────────────────────────────────────────────────────────────────────────────────
|
|
# bulk_permissions_repair.sh /mnt/user/Movies
|
|
# bulk_permissions_repair.sh /mnt/user/Movies /mnt/user/Tv_Shows
|
|
# bulk_permissions_repair.sh /mnt/user/Movies --dry-run
|
|
# bulk_permissions_repair.sh /mnt/user/Movies --log
|
|
# ==============================================================================================
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
|
|
source "$SCRIPT_DIR/../load_config.sh"
|
|
|
|
parse_args "$@"
|
|
|
|
# ==============================================================================================
|
|
# ━━━ Setup ━━━
|
|
# ==============================================================================================
|
|
if [[ "$EUID" -ne 0 ]]; then
|
|
error "Must be run as root — chown 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"
|
|
|
|
# detect_hosts() sets MY_ID — used in summary
|
|
detect_hosts
|
|
|
|
if [[ ${#PARSED_ARGS[@]} -eq 0 ]]; then
|
|
error "No paths specified"
|
|
error "Usage: bulk_permissions_repair.sh /path/to/share [/another/path] [--dry-run]"
|
|
exit 1
|
|
fi
|
|
|
|
log "Dir mode: ${PERMISSIONS_DIR_MODE:-755}"
|
|
log "File mode: ${PERMISSIONS_FILE_MODE:-664}"
|
|
log "Owner: $PERMISSIONS_OWNER"
|
|
[[ "$DRY_RUN" == true ]] && warn "DRY RUN — no permissions will be changed"
|
|
|
|
# ==============================================================================================
|
|
# ━━━ Apply Permissions ━━━
|
|
# ==============================================================================================
|
|
echo ""
|
|
echo "━━━ $ICON_PERMS Permissions Repair — $MY_ID ━━━"
|
|
|
|
START=$(date +%s)
|
|
PASS=()
|
|
FAIL=()
|
|
TOTAL_WRONG_OWNER=0
|
|
|
|
for share_path in "${PARSED_ARGS[@]}"; do
|
|
[[ -z "$share_path" ]] && continue
|
|
|
|
echo ""
|
|
echo "━━━ $ICON_PERMS $(basename "$share_path") ━━━"
|
|
|
|
if [[ ! -d "$share_path" ]]; then
|
|
error "$share_path — not found"
|
|
FAIL+=("$share_path")
|
|
continue
|
|
fi
|
|
|
|
# Count files for context — warn level so user knows what they're in for on large shares
|
|
FILE_COUNT=$(find "$share_path" -type f 2>/dev/null | wc -l)
|
|
DIR_COUNT=$(find "$share_path" -type d 2>/dev/null | wc -l)
|
|
SIZE=$(du -sh "$share_path" 2>/dev/null | cut -f1)
|
|
warn "$share_path — $FILE_COUNT files, $DIR_COUNT dirs ($SIZE)"
|
|
|
|
# Count files with wrong ownership before fixing — diagnostic
|
|
WRONG_OWNER=$(find "$share_path" \( ! -user nobody -o ! -group users \) \
|
|
2>/dev/null | wc -l)
|
|
if [[ "$WRONG_OWNER" -gt 0 ]]; then
|
|
warn "$WRONG_OWNER file(s) with wrong ownership — fixing..."
|
|
if [[ "$WRONG_OWNER" -gt 500 ]]; then
|
|
warn "High wrong-owner count — check container PUID/PGID settings (should be PUID=99 PGID=100)"
|
|
warn "Common culprits: SABnzbd, qBittorrent, slskd"
|
|
fi
|
|
TOTAL_WRONG_OWNER=$(( TOTAL_WRONG_OWNER + WRONG_OWNER ))
|
|
else
|
|
log "Ownership already correct — applying mode only"
|
|
fi
|
|
|
|
if [[ "$DRY_RUN" == true ]]; then
|
|
warn "DRY RUN — would apply:"
|
|
warn " chown -R $PERMISSIONS_OWNER $share_path"
|
|
warn " find -type d → chmod ${PERMISSIONS_DIR_MODE:-755}"
|
|
warn " find -type f → chmod ${PERMISSIONS_FILE_MODE:-664}"
|
|
PASS+=("$(basename "$share_path")")
|
|
continue
|
|
fi
|
|
|
|
CHOWN_OK=true
|
|
CHMOD_DIR_OK=true
|
|
CHMOD_FILE_OK=true
|
|
|
|
# Apply ownership first
|
|
log "Applying ownership: $PERMISSIONS_OWNER..."
|
|
chown -R "$PERMISSIONS_OWNER" "$share_path" 2>/dev/null || CHOWN_OK=false
|
|
|
|
# Apply directory permissions — separate pass (dirs need execute bit)
|
|
log "Applying directory permissions: ${PERMISSIONS_DIR_MODE:-755}..."
|
|
find "$share_path" -type d \
|
|
-exec chmod "${PERMISSIONS_DIR_MODE:-755}" {} + 2>/dev/null || CHMOD_DIR_OK=false
|
|
|
|
# Apply file permissions — no execute bit on media files
|
|
log "Applying file permissions: ${PERMISSIONS_FILE_MODE:-664}..."
|
|
find "$share_path" -type f \
|
|
-exec chmod "${PERMISSIONS_FILE_MODE:-664}" {} + 2>/dev/null || CHMOD_FILE_OK=false
|
|
|
|
if [[ "$CHOWN_OK" == true && "$CHMOD_DIR_OK" == true && "$CHMOD_FILE_OK" == true ]]; then
|
|
log "$ICON_UNLOCKED $(basename "$share_path") — permissions applied ✅"
|
|
PASS+=("$(basename "$share_path")")
|
|
else
|
|
error "$(basename "$share_path") — repair failed"
|
|
error " chown: $CHOWN_OK chmod dirs: $CHMOD_DIR_OK chmod files: $CHMOD_FILE_OK"
|
|
FAIL+=("$(basename "$share_path")")
|
|
fi
|
|
done
|
|
|
|
END=$(date +%s)
|
|
|
|
# ==============================================================================================
|
|
# ━━━ Summary ━━━
|
|
# ==============================================================================================
|
|
echo ""
|
|
echo "━━━━━ $ICON_SUMMARY PERMISSIONS REPAIR SUMMARY ━━━━━"
|
|
echo "$ICON_HOST Identity: $MY_ID ($LOCAL_SERVER_NAME)"
|
|
echo "$ICON_PERMS Dir mode: ${PERMISSIONS_DIR_MODE:-755}"
|
|
echo "$ICON_PERMS File mode: ${PERMISSIONS_FILE_MODE:-664}"
|
|
echo "$ICON_PERMS Owner: $PERMISSIONS_OWNER"
|
|
echo "$ICON_TIME Duration: $(format_duration $(( END - START )))"
|
|
echo ""
|
|
[[ ${#PASS[@]} -gt 0 ]] && log "Pass: ${PASS[*]}"
|
|
[[ ${#FAIL[@]} -gt 0 ]] && echo "$ICON_ERROR Fail: ${FAIL[*]}"
|
|
|
|
if [[ "$TOTAL_WRONG_OWNER" -gt 0 ]]; then
|
|
warn "Total wrong-owner files fixed: $TOTAL_WRONG_OWNER"
|
|
fi
|
|
echo ""
|
|
|
|
if [[ "$DRY_RUN" == true ]]; then
|
|
warn "DRY RUN — no changes made"
|
|
elif [[ ${#FAIL[@]} -gt 0 ]]; then
|
|
echo "$ICON_ERROR Status: SOME REPAIRS FAILED"
|
|
notify "Permissions repair failed on $(hostname) — ${FAIL[*]}" \
|
|
"Permissions Repair" "warning"
|
|
else
|
|
log "$ICON_DONE Status: done — ${#PASS[@]} path(s) repaired"
|
|
fi
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
|
|
[[ ${#FAIL[@]} -gt 0 ]] && exit 1
|
|
exit 0 |