Merge pull request #4954 from SillyTavern/fix/docker-readonly Update Dockerfile

cd3bed87c12fd924a44e5e9411d7b939d3cdff45

Cohee <18619528+Cohee1207@users.noreply.github.com>

Signed
2 files changed, +106 -8Showing whitespace changes
Dockerfile+9 -5
@@ -1,19 +1,21 @@
1FROM node:lts-alpine3.221FROM node:lts-alpine3.23
22
3# Arguments3# Arguments
4ARG APP_HOME=/home/node/app4ARG APP_HOME=/home/node/app
55
6# Install system dependencies6# Install system dependencies
7RUN apk add --no-cache gcompat tini git git-lfs7# Added su-exec and shadow to support optional PUID/PGID user mapping
8RUN apk add --no-cache gcompat tini git git-lfs su-exec shadow
89
9# Create app directory10# Create app directory and set ownership
10WORKDIR ${APP_HOME}11WORKDIR ${APP_HOME}
12RUN chown node:node ${APP_HOME}
1113
12# Set NODE_ENV to production14# Set NODE_ENV to production
13ENV NODE_ENV=production15ENV NODE_ENV=production
1416
15# Bundle app source17# Bundle app source and set ownership
16COPY . ./18COPY --chown=node:node . ./
1719
18RUN \20RUN \
19 echo "*** Install npm packages ***" && \21 echo "*** Install npm packages ***" && \
@@ -24,6 +26,8 @@ RUN \
24 rm -f "config.yaml" || true && \26 rm -f "config.yaml" || true && \
25 ln -s "./config/config.yaml" "config.yaml" || true && \27 ln -s "./config/config.yaml" "config.yaml" || true && \
26 mkdir "config" || true28 mkdir "config" || true
29# Set ownership
30RUN chown -R node:node config
2731
28# Pre-compile public libraries32# Pre-compile public libraries
29RUN \33RUN \
docker/docker-entrypoint.sh+97 -3
@@ -1,12 +1,106 @@
1#!/bin/sh1#!/bin/sh
22
3# Function to handle startup logic (Config check + Postinstall + Start)
4start_sillytavern() {
5 local PREFIX="$1"
6 shift # Remove the first argument (PREFIX) so $@ contains the rest
7
8 # Config Check
3 if [ ! -e "config/config.yaml" ]; then9 if [ ! -e "config/config.yaml" ]; then
4 echo "Resource not found, copying from defaults: config.yaml"10 echo "Resource not found, copying from defaults: config.yaml"
5 cp -r "default/config.yaml" "config/config.yaml"11 $PREFIX cp -r "default/config.yaml" "config/config.yaml"
6 fi12 fi
713
8 # Execute postinstall to auto-populate config.yaml with missing values14 # Execute postinstall to auto-populate config.yaml with missing values
9npm run postinstall15 $PREFIX npm run postinstall
1016
11 # Start the server17 # Start the server
12exec node server.js --listen "$@"18 exec $PREFIX node server.js --listen "$@"
19}
20
21# Dirs that MUST be present at this point (e.g for volumeless docker runs).
22# Please update list, if in the future a related perm issue appear.
23CORE_DIRS="config data plugins public/scripts/extensions/third-party"
24
25# Mounted Volumes (External)
26# Parse mounts, handling files vs directories
27RAW_MOUNTS=$(awk -v app_path="/home/node/app" '$2 ~ "^" app_path {print $2}' /proc/mounts)
28MOUNTED_DIRS=""
29
30for mount in $RAW_MOUNTS; do
31 if [ -f "$mount" ]; then
32 # If it is a mounted file (e.g. cert.pem), we want to check its PARENT directory
33 # so that the app can write adjacent files (e.g. key.pem).
34 PARENT_DIR=$(dirname "$mount")
35
36 # Performance Safety: If the file is in the root of the app,
37 # we do NOT add the parent (App Root), or we will recursively scan the whole app.
38 if [ "$PARENT_DIR" = "/home/node/app" ]; then
39 MOUNTED_DIRS="$MOUNTED_DIRS $mount"
40 else
41 MOUNTED_DIRS="$MOUNTED_DIRS $PARENT_DIR"
42 fi
43 else
44 # It is a directory, add it directly
45 MOUNTED_DIRS="$MOUNTED_DIRS $mount"
46 fi
47done
48
49# Combine dirs for checks
50CHECK_DIRS=$(echo "$CORE_DIRS $MOUNTED_DIRS" | tr ' ' '\n' | sort -u)
51
52# Ensure the needed directories exist
53for dir in $CHECK_DIRS; do
54 if [ ! -e "$dir" ]; then
55 echo "Creating missing directory: $dir"
56 mkdir -p "$dir"
57 fi
58done
59
60# Change permissions only if started as Root(UID 0) and needed.
61if [ "$(id -u)" = "0" ]; then
62 # Check if PUID/PGID variables are provided
63 if [ -n "$PUID" ] && [ -n "$PGID" ]; then
64 TARGET_UID=$PUID
65 TARGET_GID=$PGID
66 echo "Non-root mode requested (UID:$TARGET_UID GID:$TARGET_GID)."
67
68 # Update the internal 'node' user to match requested IDs
69 groupmod -o -g "$TARGET_GID" node
70 usermod -o -u "$TARGET_UID" -g "$TARGET_GID" node
71
72 for dir in $CHECK_DIRS; do
73 if [ -d "$dir" ]; then
74 # Runs chown only if there is an mismatch
75 DIR_UID=$(stat -c '%u' "$dir")
76 DIR_GID=$(stat -c '%g' "$dir")
77
78 if [ "$DIR_UID" != "$TARGET_UID" ] || [ "$DIR_GID" != "$TARGET_GID" ]; then
79 echo "(Detected mismatch) Adjusting permissions for: $dir."
80 if ! chown -R node:node "$dir"; then
81 echo "Error: Failed to update permissions for '$dir'."
82 fi
83 fi
84 fi
85 done
86
87 # Fix config file specifically
88 chown node:node "config/config.yaml" 2>/dev/null
89
90 EXEC_PREFIX="su-exec node:node"
91 else
92 # Default: Run as Root (original behavior)
93 echo "Running in default (root) mode."
94 EXEC_PREFIX=""
95 fi
96
97else
98 # Non-Root Mode (Docker CLI --user flag)
99 echo "Running as detected user (UID: $(id -u))."
100 # We CANNOT auto-fix permissions in this mode because we lack privileges.
101 # Relying solely on the user configuring their host permissions correctly.
102 EXEC_PREFIX=""
103fi
104
105# Calling function with the determined prefix
106start_sillytavern "$EXEC_PREFIX" "$@"