mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
fix: set a writable HOME for the app user so PaddleOCR works in non-root deployments (#430)
The container dropped privileges to the non-root snapotter user via gosu
(external) and s6-setuidgid (embedded), both of which preserve the
environment without setting HOME. The app therefore kept root's HOME=/root,
which is not writable by snapotter, and PaddleOCR died with
PermissionError: '/root/.paddlex/temp' -- breaking the ocr tool at default
quality in every non-root deployment. Prior GPU QA ran the app as root, which
masked it.
Fix: export HOME=/data/.home (persistent, writable, hidden) at every
privilege-drop point:
- entrypoint.sh external gosu path and non-root tini path (the latter uses
$DD/.home so a DATA_DIR override stays consistent).
- the s6 snapotter/run service (scoped there, not globally before /init, so
postgres/redis do not inherit a snapotter-owned HOME).
The root preflight creates /data/.home and the existing chown sweep owns it as
the PUID/PGID-remapped snapotter; the dir is added to both ensure_writable
probes so an unwritable HOME fails fast with the storage-permission guidance
instead of crashing late. The Dockerfile passwd home moves from /app
(read-only) to /data/.home as the getpwuid fallback when HOME is unset.
Because bridge.ts forwards HOME to the Python sidecar, this also repairs the
expanduser("~") caches in inpaint/outpaint/restore/noise_removal/remove_bg,
not just PaddleOCR.
Also fixes a test-harness inconsistency: tool-default-settings passport-photo
countryCode "us" -> "US" (the route exact-matches uppercase PASSPORT_SPECS
codes; the UI already sends "US", so users were never affected).
Claude-Session: https://claude.ai/code/session_01XGB4pGvTvb7sUX4JN745U7
This commit is contained in:
+4
-1
@@ -557,7 +557,10 @@ ENV PYTHONWARNINGS=default \
|
||||
PADDLE_PDX_DISABLE_MODEL_SOURCE_CHECK=True
|
||||
|
||||
# Create non-root user for runtime
|
||||
RUN groupadd -r snapotter && useradd -r -g snapotter -d /app -s /sbin/nologin snapotter
|
||||
# Home is /data/.home (created + chowned by the entrypoint at runtime). Runtime
|
||||
# also exports HOME; this passwd entry is the writable fallback for any code that
|
||||
# resolves ~ via getpwuid when HOME is unset (the old /app home was read-only).
|
||||
RUN groupadd -r snapotter && useradd -r -g snapotter -d /data/.home -s /sbin/nologin snapotter
|
||||
# /app and /opt/venv are read-only at runtime -> owned by snapotter.
|
||||
# /data and /tmp/workspace are written at runtime: make them group-0 (root group)
|
||||
# owned and group-writable with the setgid bit so the app can still write when the
|
||||
|
||||
+13
-4
@@ -81,8 +81,10 @@ DD="${DATA_DIR:-/data}"
|
||||
# user and fail fast with actionable guidance -- otherwise the venv bootstrap
|
||||
# and the app below would die later with a cryptic EACCES.
|
||||
if [ "$(id -u)" != "0" ]; then
|
||||
mkdir -p "$DD/files" "$DD/logs" "$DD/ai/models" "$DD/ai/pip-cache" "$DD/ai/venv" "$WS" 2>/dev/null || true
|
||||
ensure_writable "$WS" "$DD" || exit 1
|
||||
mkdir -p "$DD/files" "$DD/logs" "$DD/ai/models" "$DD/ai/pip-cache" "$DD/ai/venv" "$WS" "$DD/.home" 2>/dev/null || true
|
||||
# Group-writable so a second in-group-0 UID (OpenShift / fsGroup:0) can reuse HOME.
|
||||
chmod g+rwX "$DD/.home" 2>/dev/null || true
|
||||
ensure_writable "$WS" "$DD" "$DD/.home" || exit 1
|
||||
fi
|
||||
|
||||
# Clean up any interrupted bootstrap from a previous start
|
||||
@@ -216,7 +218,7 @@ if [ "$(id -u)" = "0" ]; then
|
||||
fi
|
||||
|
||||
# Ensure all writable subdirectories exist before chown
|
||||
mkdir -p /data/files /data/logs /data/ai/models /data/ai/pip-cache /data/ai/venv /tmp/workspace
|
||||
mkdir -p /data/files /data/logs /data/ai/models /data/ai/pip-cache /data/ai/venv /tmp/workspace /data/.home
|
||||
[ -n "${EMBEDDED_MODE:-}" ] && mkdir -p /data/redis
|
||||
|
||||
# Chown writable directories (/data is the persistent volume, /tmp/workspace is
|
||||
@@ -238,7 +240,7 @@ if [ "$(id -u)" = "0" ]; then
|
||||
# Root can write anywhere, so verify as the unprivileged snapotter user that
|
||||
# actually runs the app. This catches root-squashed or foreign-owned mounts
|
||||
# where the chown above silently failed, and fails fast with guidance.
|
||||
if ! gosu snapotter sh -c '. /usr/local/bin/entrypoint-lib.sh; ensure_writable "$@"' _ "$WS" "$DD"; then
|
||||
if ! gosu snapotter sh -c '. /usr/local/bin/entrypoint-lib.sh; ensure_writable "$@"' _ "$WS" "$DD" /data/.home; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -250,10 +252,17 @@ if [ "$(id -u)" = "0" ]; then
|
||||
fi
|
||||
# External mode: tini becomes PID 1 (reaps zombies, forwards signals) and runs
|
||||
# the app as snapotter, the same end state as the prior tini ENTRYPOINT.
|
||||
# gosu preserves the environment (unlike su), so HOME=/root would survive the
|
||||
# drop; set a writable HOME or libraries that write under ~ (PaddleOCR's
|
||||
# ~/.paddlex, plus the sidecar's expanduser caches) fail with EACCES.
|
||||
export HOME=/data/.home
|
||||
exec tini -- gosu snapotter "$@"
|
||||
fi
|
||||
|
||||
# Already running as snapotter (e.g. Kubernetes runAsUser). External mode only:
|
||||
# embedded mode requires root and exited earlier. tini becomes PID 1.
|
||||
# HOME points at the data volume (created and writability-checked in the
|
||||
# non-root preflight above) so ~-based caches land somewhere writable.
|
||||
print_banner
|
||||
export HOME="$DD/.home"
|
||||
exec tini -- "$@"
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
#!/command/with-contenv sh
|
||||
cd /app/apps/api
|
||||
# s6-setuidgid preserves the environment and does not set HOME, so the app would
|
||||
# inherit root's HOME=/root. Scope a writable HOME to this service only (not a
|
||||
# global export before /init) so postgres/redis do not inherit a snapotter-owned
|
||||
# home. Created and chowned by the entrypoint root preflight before s6 starts.
|
||||
export HOME=/data/.home
|
||||
exec s6-setuidgid snapotter ./node_modules/.bin/tsx --import ./src/tracing.ts --import ./src/instrument.ts src/index.ts
|
||||
|
||||
@@ -14,7 +14,7 @@ export const TOOL_SETTINGS_OVERRIDES: Record<string, unknown> = {
|
||||
convert: { format: "png" },
|
||||
"watermark-text": { text: "Test" },
|
||||
"text-overlay": { text: "Test" },
|
||||
"passport-photo": { countryCode: "us" },
|
||||
"passport-photo": { countryCode: "US" },
|
||||
"trim-video": { startS: 0, endS: 5 },
|
||||
"trim-audio": { startS: 0, endS: 5 },
|
||||
"split-pdf": { mode: "range", range: "1" },
|
||||
|
||||
Reference in New Issue
Block a user