#!/bin/bash # ============================================================================================== # ============================= Media Shares Permissions ======================================= # ============================================================================================== # # PURPOSE # ───────────────────────────────────────────────────────────────────────────── # Apply nobody:users ownership and correct permissions to all media shares. # Runs daily as the first job in the maintenance window — arr cleanup depends # on correct ownership to rename and delete files. # # Now a proper daily failsafe: files arrive with wrong ownership from rsync # without --chown, manual admin copies, containers with unconfigured PUID/PGID, # or unRAID environment resets after updates. # # ============================================================================================== # OPERATIONAL SAFEGUARDS # ============================================================================================== # # acquire_lock "wait" — wait if previous run still active (large share scans) # detect_hosts() — correct share list per host via MY_ID aliases # Empty array guard — warns and exits cleanly if no shares configured # Folder existence — skips missing shares with warning, continues others # Separate passes — directories and files chmod'd separately for correctness # validate_unraid_cmd — notify script validated before use # Silent by default — only failures produce output, success is silent # # Diagnostic — high corrected count on every run means a container has wrong PUID/PGID: # Correct values on unRAID: PUID=99 (nobody) PGID=100 (users) # Common culprits: SABnzbd, qBittorrent, slskd — check these first # Once fixed, this script should correct 0 files per run (pure failsafe) # # ============================================================================================== # CONFIGURATION # ============================================================================================== # # master_host*.conf # # HOST*_MEDIA_PERMISSION_SHARES — shares this host applies permissions to # Aliased by detect_hosts() — script uses MEDIA_PERMISSION_SHARES # # master.conf # # PERMISSIONS_DIR_MODE — directory permissions (default 755) # PERMISSIONS_FILE_MODE — file permissions (default 664) # PERMISSIONS_OWNER — ownership applied to all files (default nobody:users) # # ============================================================================================== # RUNTIME MODES # ============================================================================================== # # media_shares_permissions.sh — normal run # media_shares_permissions.sh --dry-run — preview without making changes # media_shares_permissions.sh --log — verbose output # media_shares_permissions.sh --status — show config and exit # # ============================================================================================== SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "$SCRIPT_DIR/../load_config.sh" parse_args "$@" # ============================================================================================== # ━━━ Setup ━━━ # ============================================================================================== echo "" echo "━━━ $ICON_GEAR Setup ━━━" if [[ "$EUID" -ne 0 ]]; then error "Must be run as 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 "wait" # detect_hosts() sets MY_ID and aliases HOST*_MEDIA_PERMISSION_SHARES detect_hosts # Empty array guard if [[ ${#MEDIA_PERMISSION_SHARES[@]} -eq 0 ]]; then warn "MEDIA_PERMISSION_SHARES is empty for $MY_ID — nothing to do" warn "Check HOST*_MEDIA_PERMISSION_SHARES in master_host*.conf" exit 0 fi [[ "$DRY_RUN" == true ]] && warn "DRY RUN — no changes will be made" # ============================================================================================== # ━━━ Status ━━━ # ============================================================================================== if [[ "$SHOW_STATUS" == true ]]; then echo "" echo "━━━━━ $ICON_SUMMARY STATUS ━━━━━" 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_PERMS Shares: ${#MEDIA_PERMISSION_SHARES[@]}" echo "" for share in "${MEDIA_PERMISSION_SHARES[@]}"; do local_status="missing" [[ -d "$share" ]] && local_status="exists" echo " $share — $local_status" done echo "$ICON_GEAR Dry Run: $DRY_RUN" echo "━━━━━━━━━━━━━━━━━━━━━━━" exit 0 fi # ============================================================================================== # ━━━ Apply Permissions ━━━ # ============================================================================================== echo "" echo "━━━ $ICON_PERMS Media Permissions — $MY_ID ━━━" log "Dir mode: ${PERMISSIONS_DIR_MODE:-755}" log "File mode: ${PERMISSIONS_FILE_MODE:-664}" log "Owner: $PERMISSIONS_OWNER" log "Shares: ${#MEDIA_PERMISSION_SHARES[@]}" echo "" START=$(date +%s) FAILED=() UPDATED=() SKIPPED=() TOTAL_DIRS_FIXED=0 TOTAL_FILES_FIXED=0 for SHARE in "${MEDIA_PERMISSION_SHARES[@]}"; do SHARE_NAME=$(basename "$SHARE") if [[ ! -d "$SHARE" ]]; then warn "$SHARE_NAME not found — skipping" SKIPPED+=("$SHARE_NAME") continue fi if [[ "$DRY_RUN" == true ]]; then # Count what would be changed without making changes DIR_COUNT=$(find "$SHARE" -type d ! -perm "${PERMISSIONS_DIR_MODE:-755}" \ 2>/dev/null | wc -l) FILE_COUNT=$(find "$SHARE" -type f ! -perm "${PERMISSIONS_FILE_MODE:-664}" \ 2>/dev/null | wc -l) OWNER_COUNT=$(find "$SHARE" ! -user nobody -o ! -group users \ 2>/dev/null | wc -l) warn "DRY RUN — $SHARE_NAME: $DIR_COUNT dirs, $FILE_COUNT files, $OWNER_COUNT ownership fixes needed" continue fi log "Updating $SHARE_NAME..." CHMOD_DIR_OK=true CHMOD_FILE_OK=true CHOWN_OK=true # Count files with wrong ownership before fixing (diagnostic) WRONG_OWNER=$(find "$SHARE" \( ! -user nobody -o ! -group users \) \ 2>/dev/null | wc -l) # Apply ownership first — affects all files and directories chown -R "$PERMISSIONS_OWNER" "$SHARE" 2>/dev/null || CHOWN_OK=false # Apply directory permissions — separate pass for correctness # Directories need execute bit — different from files find "$SHARE" -type d -exec chmod "${PERMISSIONS_DIR_MODE:-755}" {} + \ 2>/dev/null || CHMOD_DIR_OK=false # Apply file permissions — no execute bit on media files find "$SHARE" -type f -exec chmod "${PERMISSIONS_FILE_MODE:-664}" {} + \ 2>/dev/null || CHMOD_FILE_OK=false if [[ "$CHMOD_DIR_OK" == true && \ "$CHMOD_FILE_OK" == true && \ "$CHOWN_OK" == true ]]; then log "$ICON_UNLOCKED $SHARE_NAME — permissions applied" UPDATED+=("$SHARE_NAME") # Log diagnostic if many files had wrong ownership if [[ "$WRONG_OWNER" -gt 0 ]]; then warn "$SHARE_NAME — corrected $WRONG_OWNER file(s) with wrong ownership" warn "If this is high, check container PUID/PGID settings (should be PUID=99 PGID=100)" fi TOTAL_DIRS_FIXED=$(( TOTAL_DIRS_FIXED + 1 )) TOTAL_FILES_FIXED=$(( TOTAL_FILES_FIXED + WRONG_OWNER )) else error "$SHARE_NAME — permissions failed" error " chown: $CHOWN_OK chmod dirs: $CHMOD_DIR_OK chmod files: $CHMOD_FILE_OK" FAILED+=("$SHARE_NAME") fi done END=$(date +%s) # ============================================================================================== # ━━━ Summary ━━━ # ============================================================================================== echo "" echo "━━━━━ $ICON_SUMMARY MEDIA PERMISSIONS 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 "" [[ ${#UPDATED[@]} -gt 0 ]] && log "Updated: ${#UPDATED[@]} shares" [[ ${#SKIPPED[@]} -gt 0 ]] && warn "Skipped: ${SKIPPED[*]} (not found)" [[ ${#FAILED[@]} -gt 0 ]] && echo "$ICON_ERROR Failed: ${FAILED[*]}" # Diagnostic — high correction count indicates container PUID/PGID issue if [[ "$TOTAL_FILES_FIXED" -gt 50 ]]; then warn "$TOTAL_FILES_FIXED files had wrong ownership this run" warn "High count suggests a container is not set to PUID=99 PGID=100" warn "Common culprits: SABnzbd, qBittorrent, slskd — check container env vars" fi if [[ "$DRY_RUN" == true ]]; then warn "DRY RUN — no changes made" elif [[ ${#FAILED[@]} -gt 0 ]]; then echo "$ICON_ERROR Status: SOME SHARES FAILED" notify "Media permissions failed on $(hostname) — ${FAILED[*]}" \ "Media Permissions" "warning" else log "$ICON_DONE Status: done — ${#UPDATED[@]} shares updated" fi echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" [[ ${#FAILED[@]} -gt 0 ]] && exit 1 exit 0