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

cd3bed87c12fd924a44e5e9411d7b939d3cdff45

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

Signed
2 files changed, +111 -13Ignore whitespace
Dockerfile+9 -5
@@ -1,19 +1,21 @@
11FROM node:lts-alpine3.2223
22
33# Arguments
44ARG APP_HOME=/home/node/app
55
66# Install system dependencies
7-RUN apk add --no-cache gcompat tini git git-lfs
7+# Added su-exec and shadow to support optional PUID/PGID user mapping
8+RUN apk add --no-cache gcompat tini git git-lfs su-exec shadow
89
910# Create app directory and set ownership
1011WORKDIR ${APP_HOME}
12+RUN chown node:node ${APP_HOME}
1113
1214# Set NODE_ENV to production
1315ENV NODE_ENV=production
1416
1517# Bundle app source and set ownership
1618COPY --chown=node:node . ./
1719
1820RUN \
1921 echo "*** Install npm packages ***" && \
@@ -24,6 +26,8 @@ RUN \
2426 rm -f "config.yaml" || true && \
2527 ln -s "./config/config.yaml" "config.yaml" || true && \
2628 mkdir "config" || true
29+# Set ownership
30+RUN chown -R node:node config
2731
2832# Pre-compile public libraries
2933RUN \
docker/docker-entrypoint.sh+102 -8
@@ -1,12 +1,106 @@
11#!/bin/sh
22
3-if [ ! -e "config/config.yaml" ]; then
3+# Function to handle startup logic (Config check + Postinstall + Start)
4- echo "Resource not found, copying from defaults: config.yaml"
4+start_sillytavern() {
5- cp -r "default/config.yaml" "config/config.yaml"
5+ local PREFIX="$1"
6-fi
6+ shift # Remove the first argument (PREFIX) so $@ contains the rest
7+
8+ # Config Check
9+ if [ ! -e "config/config.yaml" ]; then
10+ echo "Resource not found, copying from defaults: config.yaml"
11+ $PREFIX cp -r "default/config.yaml" "config/config.yaml"
12+ fi
13+
14+ # Execute postinstall to auto-populate config.yaml with missing values
15+ $PREFIX npm run postinstall
16+
17+ # Start the server
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.
23+CORE_DIRS="config data plugins public/scripts/extensions/third-party"
24+
25+# Mounted Volumes (External)
26+# Parse mounts, handling files vs directories
27+RAW_MOUNTS=$(awk -v app_path="/home/node/app" '$2 ~ "^" app_path {print $2}' /proc/mounts)
28+MOUNTED_DIRS=""
29+
30+for 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
47+done
48+
49+# Combine dirs for checks
50+CHECK_DIRS=$(echo "$CORE_DIRS $MOUNTED_DIRS" | tr ' ' '\n' | sort -u)
751
8-# Execute postinstall to auto-populate config.yaml with missing values
52+# Ensure the needed directories exist
9-npm run postinstall
53+for dir in $CHECK_DIRS; do
54+ if [ ! -e "$dir" ]; then
55+ echo "Creating missing directory: $dir"
56+ mkdir -p "$dir"
57+ fi
58+done
59+
60+# Change permissions only if started as Root(UID 0) and needed.
61+if [ "$(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+
97+else
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=""
103+fi
10104
11105# StartCalling function with the serverdetermined prefix
12-exec node server.js --listen "$@"
106+start_sillytavern "$EXEC_PREFIX" "$@"