From 9e720f4fc7f009451f142e52ba4618fb40510efc Mon Sep 17 00:00:00 2001 From: None <190783071+CodeBormen@users.noreply.github.com> Date: Tue, 17 Mar 2026 18:27:19 -0500 Subject: [PATCH 1/2] fix: handle chown failures gracefully on external mounts DATA_DIRS chown failures (NFS, SMB/CIFS, FUSE) crashed the container under set -e. The old image never chowned these directories, so users with external mounts had a working setup that broke on upgrade with zero config changes. - Collect mkdir and chown failures per-directory instead of crashing - Emit a single consolidated warning listing all affected directories with current ownership and remediation steps - Container continues to start; Django reports at runtime if it cannot write to a specific directory - Local volumes are unaffected: chown still runs and succeeds silently --- docker/init/03-init-dispatcharr.sh | 74 +++++++++++++++++++++++++----- 1 file changed, 62 insertions(+), 12 deletions(-) diff --git a/docker/init/03-init-dispatcharr.sh b/docker/init/03-init-dispatcharr.sh index b580fe1e..2e66e3ee 100644 --- a/docker/init/03-init-dispatcharr.sh +++ b/docker/init/03-init-dispatcharr.sh @@ -1,6 +1,9 @@ #!/bin/bash -# Define directories that need to exist and be owned by PUID:PGID +# Define directories that need to exist and be owned by PUID:PGID. +# DATA_DIRS may reside on external mounts (NFS, SMB/CIFS, FUSE) where +# mkdir and chown can fail. Failures are collected and reported as a +# single consolidated warning so the container still starts. DATA_DIRS=( "/data/backups" "/data/logos" @@ -14,17 +17,24 @@ DATA_DIRS=( "/data/scripts" ) +# APP_DIRS live on the image layer and are always locally writable. APP_DIRS=( "/app/logo_cache" "/app/media" "/app/static" ) -# Create all directories -for dir in "${DATA_DIRS[@]}" "${APP_DIRS[@]}"; do +# Create app directories (image layer — always writable) +for dir in "${APP_DIRS[@]}"; do mkdir -p "$dir" done +# Create data directories, tolerating failures on external mounts +_failed_mkdir=() +for dir in "${DATA_DIRS[@]}"; do + _mkdir_err=$(mkdir -p "$dir" 2>&1) || _failed_mkdir+=("$dir ($_mkdir_err)") +done + # Ensure /app itself is owned by PUID:PGID (needed for uwsgi socket creation) if [ "$(id -u)" = "0" ] && [ -d "/app" ]; then if [ "$(stat -c '%u:%g' /app)" != "$PUID:$PGID" ]; then @@ -50,11 +60,16 @@ fi # NOTE: mac doesn't run as root, so only manage permissions # if this script is running as root if [ "$(id -u)" = "0" ]; then - # Fix data directories (non-recursive to avoid touching user files) + # Fix data directories (non-recursive to avoid touching user files). + # Failures are collected rather than fatal — directories may be on + # external mounts (NFS, SMB/CIFS, FUSE) that reject chown. + _failed_chown=() for dir in "${DATA_DIRS[@]}"; do - if [ -d "$dir" ] && [ "$(stat -c '%u:%g' "$dir")" != "$PUID:$PGID" ]; then - echo "Fixing ownership for $dir" - chown "$PUID:$PGID" "$dir" + if [ -d "$dir" ] && [ "$(stat -c '%u:%g' "$dir" 2>/dev/null)" != "$PUID:$PGID" ]; then + _chown_err=$(chown "$PUID:$PGID" "$dir" 2>&1) || { + _current_owner=$(stat -c '%u:%g' "$dir" 2>/dev/null || echo "unknown") + _failed_chown+=("$dir (current: $_current_owner, error: $_chown_err)") + } fi done @@ -70,11 +85,46 @@ if [ "$(id -u)" = "0" ]; then # No secondary check needed here — duplicating it could chown without updating # the sentinel, creating inconsistent state. - # Fix /data directory ownership (non-recursive) - if [ -d "/data" ] && [ "$(stat -c '%u:%g' /data)" != "$PUID:$PGID" ]; then - echo "Fixing ownership for /data (non-recursive)" - chown "$PUID:$PGID" /data + # Fix /data directory ownership (non-recursive). + # Tolerates failure for the same external-mount reasons as DATA_DIRS. + if [ -d "/data" ] && [ "$(stat -c '%u:%g' /data 2>/dev/null)" != "$PUID:$PGID" ]; then + _chown_err=$(chown "$PUID:$PGID" /data 2>&1) || { + _current_owner=$(stat -c '%u:%g' /data 2>/dev/null || echo "unknown") + _failed_chown+=("/data (current: $_current_owner, error: $_chown_err)") + } fi - chmod +x /data + chmod +x /data 2>/dev/null || true +fi + +# Consolidated warning for all mkdir/chown failures. +# Emitted outside the root guard so non-root mkdir failures are also reported. +if [ ${#_failed_mkdir[@]} -gt 0 ] || [ ${#_failed_chown[@]} -gt 0 ]; then + echo "" + echo "================================================================" + echo "WARNING: Some data directories could not be created or updated." + echo " This typically occurs with NFS, SMB/CIFS, or other external" + echo " mounts that restrict ownership changes." + echo "" + if [ ${#_failed_mkdir[@]} -gt 0 ]; then + echo " Could not create:" + for entry in "${_failed_mkdir[@]}"; do + echo " - $entry" + done + echo "" + fi + if [ ${#_failed_chown[@]} -gt 0 ]; then + echo " Could not set ownership to $PUID:$PGID:" + for entry in "${_failed_chown[@]}"; do + echo " - $entry" + done + echo "" + fi + echo " To fix, either:" + echo " 1. Set PUID/PGID to match your mount's owner" + echo " 2. Fix ownership on the host/NAS:" + echo " sudo chown $PUID:$PGID " + echo " 3. For SMB/CIFS: set uid=$PUID,gid=$PGID in mount options" + echo "================================================================" + echo "" fi From 7e22172070cd7d1e24de55c159b581ee8e517206 Mon Sep 17 00:00:00 2001 From: None <190783071+CodeBormen@users.noreply.github.com> Date: Tue, 17 Mar 2026 19:32:07 -0500 Subject: [PATCH 2/2] fix: remove PUID auto-detect The auto-detect read PUID/PGID from /data/db ownership (UID 102 in pre-PUID images), causing all upgrading users to run as UID 102 instead of the expected UID 1000. This broke host-side access (SSH, WinSCP), made existing DATA_DIR files unwritable, and failed comskip on recordings created before the change. --- docker/init/01-user-setup.sh | 23 +++++------------------ docker/init/03-init-dispatcharr.sh | 2 +- 2 files changed, 6 insertions(+), 19 deletions(-) diff --git a/docker/init/01-user-setup.sh b/docker/init/01-user-setup.sh index 43f0c514..522fcd8d 100644 --- a/docker/init/01-user-setup.sh +++ b/docker/init/01-user-setup.sh @@ -6,24 +6,11 @@ # runtime behavior since all postgres operations run as $POSTGRES_USER # rather than the postgres system user. -# Auto-detect PUID/PGID from existing data when not explicitly set. -# Avoids a cross-UID chown on upgrade, which would fail on restricted -# filesystems (NFS root_squash, CIFS). UID/GID 0 is excluded — PostgreSQL -# refuses to run as root. Falls through to default 1000 for new installs. -if [ -z "${PUID+x}" ] && [ -f "${POSTGRES_DIR}/PG_VERSION" ]; then - _data_uid=$(stat -c '%u' "${POSTGRES_DIR}/PG_VERSION") - if [ "$_data_uid" -ne 0 ] 2>/dev/null; then - export PUID=$_data_uid - echo "PUID not set — defaulting to existing data owner UID: $PUID" - fi -fi -if [ -z "${PGID+x}" ] && [ -f "${POSTGRES_DIR}/PG_VERSION" ]; then - _data_gid=$(stat -c '%g' "${POSTGRES_DIR}/PG_VERSION") - if [ "$_data_gid" -ne 0 ] 2>/dev/null; then - export PGID=$_data_gid - echo "PGID not set — defaulting to existing data owner GID: $PGID" - fi -fi +# Default PUID/PGID to 1000 when not explicitly set. +# The old image ran Django as UID 1000 and PostgreSQL as UID 102. Since +# DATA_DIRS and host-side files are owned by 1000, defaulting to 1000 +# preserves access for upgrading users without requiring configuration. +# The DB ownership migration (102 → 1000) is handled by 02-postgres.sh. export PUID=${PUID:-1000} export PGID=${PGID:-1000} diff --git a/docker/init/03-init-dispatcharr.sh b/docker/init/03-init-dispatcharr.sh index 2e66e3ee..05bcb192 100644 --- a/docker/init/03-init-dispatcharr.sh +++ b/docker/init/03-init-dispatcharr.sh @@ -31,6 +31,7 @@ done # Create data directories, tolerating failures on external mounts _failed_mkdir=() +_failed_chown=() for dir in "${DATA_DIRS[@]}"; do _mkdir_err=$(mkdir -p "$dir" 2>&1) || _failed_mkdir+=("$dir ($_mkdir_err)") done @@ -63,7 +64,6 @@ if [ "$(id -u)" = "0" ]; then # Fix data directories (non-recursive to avoid touching user files). # Failures are collected rather than fatal — directories may be on # external mounts (NFS, SMB/CIFS, FUSE) that reject chown. - _failed_chown=() for dir in "${DATA_DIRS[@]}"; do if [ -d "$dir" ] && [ "$(stat -c '%u:%g' "$dir" 2>/dev/null)" != "$PUID:$PGID" ]; then _chown_err=$(chown "$PUID:$PGID" "$dir" 2>&1) || {