fix(docker): strip tini -g flags in legacy entrypoint shim

A plain /usr/bin/tini → /init symlink forwarded tini's -g into
s6-overlay's rc.init as the container CMD, causing boot loops after
image updates that preserve old entrypoints (#66679).
This commit is contained in:
HexLab98 2026-07-18 14:37:40 +07:00 committed by Teknium
parent 3d9be27895
commit be3c160a85
2 changed files with 102 additions and 11 deletions

View file

@ -73,17 +73,19 @@ RUN set -eu; \
tar -C / -Jxpf /tmp/s6-overlay-noarch.tar.xz; \
tar -C / -Jxpf /tmp/s6-overlay-arch.tar.xz; \
tar -C / -Jxpf /tmp/s6-overlay-symlinks-noarch.tar.xz; \
rm /tmp/s6-overlay-*.tar.xz /tmp/s6-overlay.sha256; \
# #34192: backward-compat shim for orchestration templates that still\
# reference the legacy /usr/bin/tini entrypoint (e.g. Hostinger's\
# 'Hermes WebUI' catalog). The image has moved to s6-overlay /init\
# as PID 1 (see ENTRYPOINT below + the migration comment at the top\
# of this file), but external wrappers pinned to /usr/bin/tini will\
# crash with 'tini: No such file or directory' on startup. The shim\
# symlinks /usr/bin/tini -> /init so legacy wrappers exec the right\
# PID-1 reaper without behavior change for users on the current\
# ENTRYPOINT. Safe to drop once the affected catalogs are updated.\
ln -sf /init /usr/bin/tini
rm /tmp/s6-overlay-*.tar.xz /tmp/s6-overlay.sha256
# #34192 / #66679: backward-compat shim for orchestration templates that
# still reference the legacy /usr/bin/tini entrypoint (Hostinger's
# 'Hermes WebUI' catalog, NAS compose projects that preserve an old
# entrypoint on image update, etc.). A plain symlink to /init made the
# path exist, but forwarded tini flags like `-g` into s6-overlay's
# rc.init as the container CMD (`rc.init: 91: -g: not found`) and
# boot-looped any `restart: unless-stopped` deploy. The shim strips the
# tini CLI surface, then exec's /init + main-wrapper — see
# docker/tini-shim.sh. Safe to drop once the affected catalogs are
# updated.
COPY --chmod=0755 docker/tini-shim.sh /usr/bin/tini
# Non-root user for runtime; UID can be overridden via HERMES_UID at runtime
RUN useradd -u 10000 -m -d /opt/data hermes

89
docker/tini-shim.sh Executable file
View file

@ -0,0 +1,89 @@
#!/bin/sh
# shellcheck shell=sh
# /usr/bin/tini — compatibility shim for legacy orchestration templates.
#
# Background
# ----------
# The published image used to ship real `tini` as PID 1. After the
# s6-overlay migration, PID 1 is `/init`. Downstream catalogs (Hostinger
# Hermes WebUI, NAS "update" flows that preserve an old entrypoint, etc.)
# still pin entrypoints like:
#
# ["/usr/bin/tini", "-g", "--"]
# ["/usr/bin/tini", "-g", "--", "gateway", "run"]
#
# A plain `ln -sf /init /usr/bin/tini` (#34192) made the binary exist, but
# forwarded tini's own flags into s6-overlay. `/init` then handed `-g` to
# `rc.init` as the container CMD, which fails with:
#
# /run/s6/basedir/scripts/rc.init: 91: -g: not found
#
# …and with `restart: unless-stopped` the container boot-loops forever
# (#66679).
#
# This shim strips the tini CLI surface, then exec's `/init` with the
# image's main-wrapper so privilege drop + arg routing still apply.
#
# Tini flags we discard (see krallin/tini --help):
# -g / --kill-process-group, -s / --subreaper, -w, -v (repeatable),
# -h / --help, -l, --version, -p SIGNAL, -e EXIT_CODE, and `--`.
# s6-overlay already reaps zombies and forwards signals as PID 1; the
# flag semantics are intentionally no-ops here.
#
# Test hook: HERMES_TINI_SHIM_TARGET overrides the `/init` path (and
# HERMES_TINI_SHIM_WRAPPER the main-wrapper path) so unit tests can
# record argv without a real s6 tree.
set -e
INIT_TARGET="${HERMES_TINI_SHIM_TARGET:-/init}"
WRAPPER="${HERMES_TINI_SHIM_WRAPPER:-/opt/hermes/docker/main-wrapper.sh}"
while [ "$#" -gt 0 ]; do
case "$1" in
--)
shift
break
;;
-g|--kill-process-group|-s|--subreaper|-w|-l|-h|--help|--version)
shift
;;
-v)
# tini allows repeated -v; drop them all.
shift
;;
-p|-e)
# These take a mandatory argument.
shift
if [ "$#" -gt 0 ]; then
shift
fi
;;
-*)
# Unknown short/long option from an older/newer tini — drop
# rather than forwarding into /init (would recreate #66679).
shift
;;
*)
break
;;
esac
done
# No program left (entrypoint was just `tini -g --`) → same as the
# image's default ENTRYPOINT + empty CMD.
if [ "$#" -eq 0 ]; then
exec "$INIT_TARGET" "$WRAPPER"
fi
# Caller already supplied the wrapper or /init — don't double-wrap.
case "$1" in
/init|"$INIT_TARGET")
exec "$@"
;;
"$WRAPPER"|*/main-wrapper.sh)
exec "$INIT_TARGET" "$@"
;;
esac
exec "$INIT_TARGET" "$WRAPPER" "$@"