Files
Varaverk/Media/media_shares_permissions.sh
T
Gmer4Lfe 27bfc21cb0 Platform-agnostic refactor: eliminate OS-specific hardcodes from core scripts
All bash scripts are now platform-neutral. Unraid-specific paths, commands,
and service checks moved to Plugin/unraid/adapter.sh. Core scripts call
platform_*() functions exclusively — no direct OS paths in runtime logic.

New adapter functions: platform_storage_path, platform_webui_install_path,
platform_scripts_dir_probe_cmd, platform_setup_db_path, platform_storage_healthy,
platform_is_service_enabled, platform_get_temp_thresholds, platform_disk_states_path,
platform_rebuild_container, platform_push_conf, platform_push_setup_state,
platform_get_templates_dir, platform_send_os_notification.

Partnership services stack (Emby/Jellyfin/Seerr/SeerrFin) added as third
onboarding stack alongside auth and arr stacks.
2026-06-14 00:59:19 -04:00

228 lines
9.8 KiB
Bash
Executable File

#!/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
# platform_require_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
# ==============================================================================================
#
# 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 ━━━
# ==============================================================================================
if [[ "$EUID" -ne 0 ]]; then
error "Must be run as root"
exit 1
fi
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 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
# Ignore "No such file" errors: race condition with volatile dirs (e.g. Emby transcodes)
_chmod_errs=$(find "$SHARE" -type f -exec chmod "${PERMISSIONS_FILE_MODE:-664}" {} + 2>&1 | \
grep -v "No such file or directory" | grep -c "chmod:" || true)
[[ "$_chmod_errs" -gt 0 ]] && 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
echo "$ICON_DONE Status: done — ${#UPDATED[@]} shares updated"
fi
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
[[ ${#FAILED[@]} -gt 0 ]] && exit 1
exit 0