Files
sencho/docker-entrypoint.sh
Anso 9eb945a6f0 fix: run as root by default to eliminate stack-folder permission failures (#501)
Every filesystem operation against user compose folders (save, create,
deploy, update, rollback, template install, fleet snapshot restore)
previously failed with EACCES whenever a stack container had chowned
its own bind mount to another UID, which is extremely common with
linuxserver/* images and anything that runs as root by default.

Running Sencho as root eliminates the entire class of permission bugs
at the source and matches the default posture of Portainer, Dockge,
Komodo, and Yacht. Mounting /var/run/docker.sock is already equivalent
to root-on-host, so the previous non-root hardening provided essentially
no additional isolation while breaking real features.

Changes:

- docker-entrypoint.sh: default path stays root, no GID dance, no
  privilege drop. Opt-out via SENCHO_USER=sencho restores the legacy
  behavior bit-for-bit (chown data dir, match Docker socket GID,
  su-exec to the user). Fails fast if SENCHO_USER names a nonexistent
  account. Kubernetes / OpenShift forced-non-root compat preserved via
  the existing id -u = 0 guard.
- FileSystemService: delete forceDeleteViaDocker (the ~40-line helper
  that shelled out to an alpine container to work around EACCES during
  deleteStack) and simplify deleteStack to a single fsPromises.rm call.
  Tests updated accordingly.
- Dockerfile: keep the sencho user+group pre-created so the opt-out
  path works out of the box; comments updated to document the new
  default.
- Docs: new "Container user" section in configuration.mdx documenting
  the root default and the SENCHO_USER opt-out; troubleshooting and
  self-hosting updated to match.
2026-04-10 21:35:31 -04:00

68 lines
3.2 KiB
Bash

#!/bin/sh
set -e
# Resolve the data directory, mirroring DatabaseService.ts logic.
DATA_DIR="${DATA_DIR:-/app/data}"
# Sencho runs as root by default inside the container. It needs access to
# /var/run/docker.sock (which is equivalent to root on the host regardless
# of the user Sencho itself runs as) and to user-supplied bind mounts that
# containers in those stacks commonly chown to arbitrary UIDs. Running as
# root is the same posture used by Portainer, Dockge, Komodo, and Yacht.
#
# Users who need the container to drop privileges (organisational policy,
# compliance scanners, rootless Docker with UID mapping) can set
# SENCHO_USER=sencho at runtime to opt into the legacy non-root mode. The
# opt-out path does all the work the old entrypoint used to do: fix data
# volume ownership, match the Docker socket GID, and exec via su-exec.
#
# The "id -u = 0" guard keeps Kubernetes / OpenShift forced-non-root
# deployments (runAsNonRoot: true with a random high UID) working: the
# entire setup block is skipped and the app exec's directly.
if [ "$(id -u)" = '0' ]; then
mkdir -p "$DATA_DIR"
# Restrict encryption key to owner-only access regardless of runtime user.
[ -f "$DATA_DIR/encryption.key" ] && chmod 600 "$DATA_DIR/encryption.key"
if [ -n "$SENCHO_USER" ]; then
# Fail fast if the opted-in user does not exist inside the container.
# Without this, the su-exec call below would error out with a cryptic
# "su-exec: getpwnam($SENCHO_USER): No such file or directory" and the
# operator would have no hint that the variable itself is the cause.
if ! id "$SENCHO_USER" >/dev/null 2>&1; then
echo "[entrypoint] ERROR: SENCHO_USER=$SENCHO_USER does not exist inside the container." >&2
echo "[entrypoint] Use 'sencho' (pre-created) or unset SENCHO_USER to run as root." >&2
exit 1
fi
# Re-own the data dir so SQLite and the encryption key are readable
# after the privilege drop. `|| true` tolerates a read-only /app/data
# (rare but possible with some bind-mount configurations); chown
# errors still surface in the log so operators see them.
find "$DATA_DIR" \( \! -user "$SENCHO_USER" -o \! -group "$SENCHO_USER" \) \
-exec chown "$SENCHO_USER:$SENCHO_USER" '{}' + || true
# Match the Docker socket GID so the dropped user can reach Docker.
if [ -S /var/run/docker.sock ]; then
DOCKER_SOCK_GID=$(stat -c '%g' /var/run/docker.sock)
if [ "$DOCKER_SOCK_GID" = "0" ]; then
addgroup "$SENCHO_USER" root 2>/dev/null || true
else
if ! getent group "$DOCKER_SOCK_GID" > /dev/null 2>&1; then
addgroup -S -g "$DOCKER_SOCK_GID" docker-host
fi
DOCKER_GROUP=$(getent group "$DOCKER_SOCK_GID" | cut -d: -f1)
addgroup "$SENCHO_USER" "$DOCKER_GROUP" 2>/dev/null || true
fi
fi
echo "[entrypoint] SENCHO_USER=$SENCHO_USER set; dropping privileges."
exec su-exec "$SENCHO_USER" "$@"
fi
echo "[entrypoint] Running as root. Set SENCHO_USER=sencho to drop privileges."
fi
exec "$@"