diff --git a/docker-compose.yml b/docker-compose.yml index ebcdfa1..46d108f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -49,6 +49,15 @@ services: PUID: 1000 PGID: 1000 + # ======================================================================== + # OPTIONAL: File Permission Mask + # ======================================================================== + # Set a umask to control default file permissions for all files created + # by the application. Common values: + # - 002: Group-writable (files: 664, dirs: 775) - recommended for shared access + # - 022: Group-readable only (files: 644, dirs: 755) - more restrictive + # UMASK: "002" + # ======================================================================== # OPTIONAL: Secrets (auto-generated on first run if not provided) # ======================================================================== diff --git a/docker/unified/app-start.sh b/docker/unified/app-start.sh index ebf9b74..e3ffcc4 100644 --- a/docker/unified/app-start.sh +++ b/docker/unified/app-start.sh @@ -22,6 +22,12 @@ PGID=${PGID:-$(id -g node)} echo "[App] Starting Next.js server..." echo "[App] Process will run as UID:GID = $PUID:$PGID" +# Apply UMASK if set (controls default file permissions) +if [ -n "$UMASK" ]; then + echo "[App] Applying umask: $UMASK" + umask "$UMASK" +fi + cd /app # ============================================================================= diff --git a/docker/unified/entrypoint.sh b/docker/unified/entrypoint.sh index 76fb8c2..7bd08fe 100644 --- a/docker/unified/entrypoint.sh +++ b/docker/unified/entrypoint.sh @@ -387,6 +387,7 @@ PORT=$PORT HOSTNAME=$HOSTNAME PUID=${PUID:-} PGID=${PGID:-} +UMASK=${UMASK:-} ROOTLESS_CONTAINER=${ROOTLESS_CONTAINER:-} EOF diff --git a/src/app/admin/settings/lib/types.ts b/src/app/admin/settings/lib/types.ts index bbb4070..264c374 100644 --- a/src/app/admin/settings/lib/types.ts +++ b/src/app/admin/settings/lib/types.ts @@ -102,6 +102,8 @@ export interface PathsSettings { chapterMergingEnabled: boolean; fileRenameEnabled: boolean; fileRenameTemplate?: string; + fileChmod?: string; + dirChmod?: string; } /** diff --git a/src/app/admin/settings/tabs/PathsTab/PathsTab.tsx b/src/app/admin/settings/tabs/PathsTab/PathsTab.tsx index d1f3646..a1000f9 100644 --- a/src/app/admin/settings/tabs/PathsTab/PathsTab.tsx +++ b/src/app/admin/settings/tabs/PathsTab/PathsTab.tsx @@ -439,6 +439,54 @@ export function PathsTab({ paths, onChange, onValidationChange }: PathsTabProps) + {/* File Permissions */} +
+

+ File Permissions +

+

+ Octal permissions applied when organizing files into the media library. These may be further restricted by the container's UMASK setting. +

+
+
+ + updatePath('fileChmod', e.target.value)} + placeholder="664" + className={`font-mono max-w-32 ${paths.fileChmod && !/^[0-7]{3,4}$/.test(paths.fileChmod) ? 'border-red-500 dark:border-red-500' : ''}`} + /> + {paths.fileChmod && !/^[0-7]{3,4}$/.test(paths.fileChmod) && ( +

Must be 3-4 octal digits (0-7)

+ )} +

+ e.g. 664 = owner/group read-write, others read +

+
+
+ + updatePath('dirChmod', e.target.value)} + placeholder="775" + className={`font-mono max-w-32 ${paths.dirChmod && !/^[0-7]{3,4}$/.test(paths.dirChmod) ? 'border-red-500 dark:border-red-500' : ''}`} + /> + {paths.dirChmod && !/^[0-7]{3,4}$/.test(paths.dirChmod) && ( +

Must be 3-4 octal digits (0-7)

+ )} +

+ e.g. 775 = owner/group full access, others read-execute +

+
+
+
+ {/* Test Paths Button */}