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 */}