fix(docker): reject unsupported --user <arbitrary-uid> start with clear guidance (#38579)
`docker run --user $(id -u):$(id -g)` was a tini-era trick to make container-written files match the host user. Under s6-overlay it no longer works: the bootstrap (UID remap, volume + build-tree chown, config seeding) needs root, and the baked image dirs (/opt/data, /opt/hermes/.venv, ui-tui, node_modules) are owned by the hermes build UID (10000). A pinned arbitrary UID can't write them, so the runtime fails with EACCES on a bind mount or hard-crashes on a named volume (Docker inits the volume from the image as 10000; the non-root start can't even `cd /opt/data`, and the profile reconciler dies with PermissionError on gateway_state.json). Detect that start early in both the cont-init hook (stage2-hook.sh) and the CMD wrapper (main-wrapper.sh) and fail fast with actionable guidance pointing at the supported path: root start + HERMES_UID/HERMES_GID (or the PUID/PGID aliases), which remaps the hermes user and chowns the volume — the same host-UID-matching outcome --user was used for, without breaking s6. The guard fires only when the current UID is neither root NOR the hermes UID. This preserves the supported non-root start from #34648/#34837 (running with `--user 10000:10000`, i.e. pinned to the hermes UID itself), which is unaffected — only the arbitrary-UID variant that #34837 never actually made writable is rejected. Verified live across five scenarios (built image, bind + named volume): arbitrary --user on bind -> rejected with guidance, hermes does not run; arbitrary --user on named volume -> guidance shown, no raw 'can't cd' crash; --user 10000:10000 -> boots; root + HERMES_UID=4242 remap -> boots, guard not tripped; default root start -> boots. Pre-fix control reproduces the raw PermissionError + 'can't cd' crash with no guidance.
This commit is contained in:
@@ -21,6 +21,34 @@ set -e
|
||||
|
||||
drop() { [ "$(id -u)" = 0 ] && set -- s6-setuidgid hermes "$@"; exec "$@"; }
|
||||
|
||||
# --- Reject the unsupported `docker run --user <uid>:<gid>` start ---
|
||||
# Mirror the guard in stage2-hook.sh (cont-init). This is the surface the
|
||||
# user actually sees in `docker run` output: when the container is pinned to
|
||||
# an arbitrary non-root, non-hermes UID, the bootstrap was skipped and the
|
||||
# baked image dirs (owned by the hermes build UID) are unwritable, so fail
|
||||
# fast here with actionable guidance rather than crashing on `cd`/EACCES
|
||||
# further down. See stage2-hook.sh for the full rationale.
|
||||
cur_uid="$(id -u)"
|
||||
if [ "$cur_uid" != 0 ] && [ "$cur_uid" != "$(id -u hermes)" ]; then
|
||||
cat >&2 <<EOF
|
||||
[hermes] ERROR: container started with --user $cur_uid (an arbitrary, non-hermes UID) — not supported.
|
||||
|
||||
To make container-written files match your HOST user, don't use --user.
|
||||
Start as root (the default) and pass your host UID/GID instead:
|
||||
|
||||
docker run -e HERMES_UID=\$(id -u) -e HERMES_GID=\$(id -g) ...
|
||||
|
||||
NAS users (Synology / unRAID / UGOS) can use the PUID/PGID aliases:
|
||||
|
||||
docker run -e PUID=\$(id -u) -e PGID=\$(id -g) ...
|
||||
|
||||
The image remaps the hermes user to that UID/GID at boot and chowns the data
|
||||
volume, so files land owned by your host user — the same outcome --user gave,
|
||||
without breaking the s6 supervision tree.
|
||||
EOF
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# HOME comes through with-contenv as /root (the /init context). Override
|
||||
# to the hermes user's home before dropping privileges so libraries that
|
||||
# resolve paths via $HOME (e.g. discord lockfile under XDG_STATE_HOME)
|
||||
|
||||
Reference in New Issue
Block a user